Beispiel #1
0
    async def sign(self, spend_bundle: SpendBundle) -> SpendBundle:
        sigs: List[G2Element] = []
        for spend in spend_bundle.coin_spends:
            matched, puzzle_args = match_cat_puzzle(
                spend.puzzle_reveal.to_program())
            if matched:
                _, _, inner_puzzle = puzzle_args
                puzzle_hash = inner_puzzle.get_tree_hash()
                pubkey, private = await self.wallet_state_manager.get_keys(
                    puzzle_hash)
                synthetic_secret_key = calculate_synthetic_secret_key(
                    private, DEFAULT_HIDDEN_PUZZLE_HASH)
                error, conditions, cost = conditions_dict_for_solution(
                    spend.puzzle_reveal.to_program(),
                    spend.solution.to_program(),
                    self.wallet_state_manager.constants.MAX_BLOCK_COST_CLVM,
                )
                if conditions is not None:
                    synthetic_pk = synthetic_secret_key.get_g1()
                    for pk, msg in pkm_pairs_for_conditions_dict(
                            conditions, spend.coin.name(),
                            self.wallet_state_manager.constants.
                            AGG_SIG_ME_ADDITIONAL_DATA):
                        try:
                            assert bytes(synthetic_pk) == pk
                            sigs.append(
                                AugSchemeMPL.sign(synthetic_secret_key, msg))
                        except AssertionError:
                            raise ValueError(
                                "This spend bundle cannot be signed by the CAT wallet"
                            )

        agg_sig = AugSchemeMPL.aggregate(sigs)
        return SpendBundle.aggregate([spend_bundle, SpendBundle([], agg_sig)])
Beispiel #2
0
    async def load_attest_files_for_recovery_spend(self, filenames):
        spend_bundle_list = []
        info_dict = {}
        try:
            for i in filenames:
                f = open(i)
                info = f.read().split(":")
                info_dict[info[0]] = [
                    bytes.fromhex(info[2]),
                    bytes.fromhex(info[3]),
                    uint64(info[4]),
                ]

                new_sb = SpendBundle.from_bytes(bytes.fromhex(info[1]))
                spend_bundle_list.append(new_sb)
                f.close()
            # info_dict {0xidentity: "(0xparent_info 0xinnerpuz amount)"}
            my_recovery_list: List[bytes] = self.did_info.backup_ids

            # convert info dict into recovery list - same order as wallet
            info_list = []
            for entry in my_recovery_list:
                if entry.hex() in info_dict:
                    info_list.append([
                        info_dict[entry.hex()][0],
                        info_dict[entry.hex()][1],
                        info_dict[entry.hex()][2],
                    ])
                else:
                    info_list.append([])
            message_spend_bundle = SpendBundle.aggregate(spend_bundle_list)
            return info_list, message_spend_bundle
        except Exception:
            raise
Beispiel #3
0
    def aggregate(cls, offers: List["Offer"]) -> "Offer":
        total_requested_payments: Dict[Optional[bytes32],
                                       List[NotarizedPayment]] = {}
        total_bundle = SpendBundle([], G2Element())
        for offer in offers:
            # First check for any overlap in inputs
            total_inputs: Set[Coin] = {
                cs.coin
                for cs in total_bundle.coin_spends
            }
            offer_inputs: Set[Coin] = {
                cs.coin
                for cs in offer.bundle.coin_spends
            }
            if total_inputs & offer_inputs:
                raise ValueError("The aggregated offers overlap inputs")

            # Next, do the aggregation
            for tail, payments in offer.requested_payments.items():
                if tail in total_requested_payments:
                    total_requested_payments[tail].extend(payments)
                else:
                    total_requested_payments[tail] = payments

            total_bundle = SpendBundle.aggregate([total_bundle, offer.bundle])

        return cls(total_requested_payments, total_bundle)
Beispiel #4
0
    def to_spend_bundle(self) -> SpendBundle:
        # Before we serialze this as a SpendBundle, we need to serialze the `requested_payments` as dummy CoinSpends
        additional_coin_spends: List[CoinSpend] = []
        for tail_hash, payments in self.requested_payments.items():
            puzzle_reveal: Program = construct_cat_puzzle(
                CAT_MOD, tail_hash, OFFER_MOD) if tail_hash else OFFER_MOD
            inner_solutions = []
            nonces: List[bytes32] = [p.nonce for p in payments]
            for nonce in list(
                    dict.fromkeys(nonces)):  # dedup without messing with order
                nonce_payments: List[NotarizedPayment] = list(
                    filter(lambda p: p.nonce == nonce, payments))
                inner_solutions.append(
                    (nonce, [np.as_condition_args() for np in nonce_payments]))

            additional_coin_spends.append(
                CoinSpend(
                    Coin(
                        ZERO_32,
                        puzzle_reveal.get_tree_hash(),
                        uint64(0),
                    ),
                    puzzle_reveal,
                    Program.to(inner_solutions),
                ))

        return SpendBundle.aggregate([
            SpendBundle(additional_coin_spends, G2Element()),
            self.bundle,
        ])
Beispiel #5
0
    async def test_stealing_fee(self, two_nodes):
        reward_ph = WALLET_A.get_new_puzzlehash()
        full_node_1, full_node_2, server_1, server_2 = two_nodes
        blocks = await full_node_1.get_all_full_blocks()
        start_height = blocks[-1].height
        blocks = bt.get_consecutive_blocks(
            5,
            block_list_input=blocks,
            guarantee_transaction_block=True,
            farmer_reward_puzzle_hash=reward_ph,
            pool_reward_puzzle_hash=reward_ph,
        )

        full_node_1, full_node_2, server_1, server_2 = two_nodes
        peer = await connect_and_get_peer(server_1, server_2)

        for block in blocks:
            await full_node_1.full_node.respond_block(
                full_node_protocol.RespondBlock(block))

        await time_out_assert(60, node_height_at_least, True, full_node_1,
                              start_height + 5)

        receiver_puzzlehash = BURN_PUZZLE_HASH

        cvp = ConditionWithArgs(ConditionOpcode.RESERVE_FEE,
                                [int_to_bytes(10)])
        dic = {cvp.opcode: [cvp]}

        fee = 9

        coin_1 = list(blocks[-2].get_included_reward_coins())[0]
        coin_2 = None
        for coin in list(blocks[-1].get_included_reward_coins()):
            if coin.amount == coin_1.amount:
                coin_2 = coin
        spend_bundle1 = generate_test_spend_bundle(coin_1, dic, uint64(fee))

        steal_fee_spendbundle = WALLET_A.generate_signed_transaction(
            coin_1.amount + fee - 4, receiver_puzzlehash, coin_2)

        assert spend_bundle1 is not None
        assert steal_fee_spendbundle is not None

        combined = SpendBundle.aggregate(
            [spend_bundle1, steal_fee_spendbundle])

        assert combined.fees() == 4

        tx1: full_node_protocol.RespondTransaction = full_node_protocol.RespondTransaction(
            spend_bundle1)

        await full_node_1.respond_transaction(tx1, peer)

        mempool_bundle = full_node_1.full_node.mempool_manager.get_spendbundle(
            spend_bundle1.name())

        assert mempool_bundle is None
    async def do_spend(
        self,
        sim: SpendSim,
        sim_client: SimClient,
        tail: Program,
        coins: List[Coin],
        lineage_proofs: List[Program],
        inner_solutions: List[Program],
        expected_result: Tuple[MempoolInclusionStatus, Err],
        reveal_limitations_program: bool = True,
        signatures: List[G2Element] = [],
        extra_deltas: Optional[List[int]] = None,
        additional_spends: List[SpendBundle] = [],
        limitations_solutions: Optional[List[Program]] = None,
        cost_str: str = "",
    ):
        if limitations_solutions is None:
            limitations_solutions = [Program.to([])] * len(coins)
        if extra_deltas is None:
            extra_deltas = [0] * len(coins)

        spendable_cat_list: List[SpendableCAT] = []
        for coin, innersol, proof, limitations_solution, extra_delta in zip(
            coins, inner_solutions, lineage_proofs, limitations_solutions, extra_deltas
        ):
            spendable_cat_list.append(
                SpendableCAT(
                    coin,
                    tail.get_tree_hash(),
                    acs,
                    innersol,
                    limitations_solution=limitations_solution,
                    lineage_proof=proof,
                    extra_delta=extra_delta,
                    limitations_program_reveal=tail if reveal_limitations_program else Program.to([]),
                )
            )

        spend_bundle: SpendBundle = unsigned_spend_bundle_for_spendable_cats(
            CAT_MOD,
            spendable_cat_list,
        )
        agg_sig = AugSchemeMPL.aggregate(signatures)
        result = await sim_client.push_tx(
            SpendBundle.aggregate(
                [
                    *additional_spends,
                    spend_bundle,
                    SpendBundle([], agg_sig),  # "Signing" the spend bundle
                ]
            )
        )
        assert result == expected_result
        self.cost[cost_str] = cost_of_spend_bundle(spend_bundle)
        await sim.farm_block()
    async def generate_issuance_bundle(
            cls, wallet, _: Dict,
            amount: uint64) -> Tuple[TransactionRecord, SpendBundle]:
        coins = await wallet.standard_wallet.select_coins(amount)

        origin = coins.copy().pop()
        origin_id = origin.name()

        cc_inner: Program = await wallet.get_new_inner_puzzle()
        await wallet.add_lineage(origin_id, LineageProof())
        genesis_coin_checker: Program = cls.construct([Program.to(origin_id)])

        minted_cc_puzzle_hash: bytes32 = construct_cat_puzzle(
            CAT_MOD, genesis_coin_checker.get_tree_hash(),
            cc_inner).get_tree_hash()

        tx_record: TransactionRecord = await wallet.standard_wallet.generate_signed_transaction(
            amount, minted_cc_puzzle_hash, uint64(0), origin_id, coins)
        assert tx_record.spend_bundle is not None

        inner_solution = wallet.standard_wallet.add_condition_to_solution(
            Program.to([51, 0, -113, genesis_coin_checker, []]),
            wallet.standard_wallet.make_solution(primaries=[{
                "puzzlehash":
                cc_inner.get_tree_hash(),
                "amount":
                amount
            }], ),
        )
        eve_spend = unsigned_spend_bundle_for_spendable_cats(
            CAT_MOD,
            [
                SpendableCAT(
                    list(
                        filter(lambda a: a.amount == amount,
                               tx_record.additions))[0],
                    genesis_coin_checker.get_tree_hash(),
                    cc_inner,
                    inner_solution,
                    limitations_program_reveal=genesis_coin_checker,
                )
            ],
        )
        signed_eve_spend = await wallet.sign(eve_spend)

        if wallet.cat_info.my_tail is None:
            await wallet.save_info(
                CATInfo(genesis_coin_checker.get_tree_hash(),
                        genesis_coin_checker, wallet.cat_info.lineage_proofs),
                False,
            )

        return tx_record, SpendBundle.aggregate(
            [tx_record.spend_bundle, signed_eve_spend])
Beispiel #8
0
        def test_fun(coin_1: Coin, coin_2: Coin) -> SpendBundle:
            announce = Announcement(coin_2.name(), b"test")
            cvp = ConditionWithArgs(ConditionOpcode.ASSERT_COIN_ANNOUNCEMENT, [announce.name()])
            dic = {cvp.opcode: [cvp]}

            cvp2 = ConditionWithArgs(ConditionOpcode.CREATE_COIN_ANNOUNCEMENT, [b"test"])
            dic2 = {cvp.opcode: [cvp2]}
            spend_bundle1 = generate_test_spend_bundle(coin_1, dic)
            spend_bundle2 = generate_test_spend_bundle(coin_2, dic2)
            bundle = SpendBundle.aggregate([spend_bundle1, spend_bundle2])
            return bundle
Beispiel #9
0
    async def generate_new_decentralised_id(
            self, amount: uint64) -> Optional[SpendBundle]:
        """
        This must be called under the wallet state manager lock
        """

        coins = await self.standard_wallet.select_coins(amount)
        if coins is None:
            return None

        origin = coins.copy().pop()

        did_inner: Program = await self.get_new_innerpuz()
        did_inner_hash = did_inner.get_tree_hash()
        did_puz = did_wallet_puzzles.create_fullpuz(did_inner,
                                                    origin.puzzle_hash)
        did_puzzle_hash = did_puz.get_tree_hash()

        tx_record: Optional[
            TransactionRecord] = await self.standard_wallet.generate_signed_transaction(
                amount, did_puzzle_hash, uint64(0), origin.name(), coins)
        eve_coin = Coin(origin.name(), did_puzzle_hash, amount)
        future_parent = CCParent(
            eve_coin.parent_coin_info,
            did_inner_hash,
            eve_coin.amount,
        )
        eve_parent = CCParent(
            origin.parent_coin_info,
            origin.puzzle_hash,
            origin.amount,
        )
        await self.add_parent(eve_coin.parent_coin_info, eve_parent, False)
        await self.add_parent(eve_coin.name(), future_parent, False)

        if tx_record is None or tx_record.spend_bundle is None:
            return None

        # Only want to save this information if the transaction is valid
        did_info: DIDInfo = DIDInfo(
            origin,
            self.did_info.backup_ids,
            self.did_info.num_of_backup_ids_needed,
            self.did_info.parent_info,
            did_inner,
            None,
            None,
            None,
        )
        await self.save_info(did_info, False)
        eve_spend = await self.generate_eve_spend(eve_coin, did_puz, did_inner)
        full_spend = SpendBundle.aggregate([tx_record.spend_bundle, eve_spend])
        return full_spend
def test_only_odd_coins_0():
    blocks = initial_blocks()
    farmed_coin = list(blocks[-1].get_included_reward_coins())[0]

    metadata = [("foo", "bar")]
    ANYONE_CAN_SPEND_PUZZLE = Program.to(1)
    launcher_amount = uint64(1)
    launcher_puzzle = LAUNCHER_PUZZLE
    launcher_puzzle_hash = launcher_puzzle.get_tree_hash()
    initial_singleton_puzzle = adaptor_for_singleton_inner_puzzle(
        ANYONE_CAN_SPEND_PUZZLE)
    lineage_proof, launcher_id, condition_list, launcher_spend_bundle = launcher_conditions_and_spend_bundle(
        farmed_coin.name(), launcher_amount, initial_singleton_puzzle,
        metadata, launcher_puzzle)

    conditions = Program.to(condition_list)
    coin_solution = CoinSolution(farmed_coin, ANYONE_CAN_SPEND_PUZZLE,
                                 conditions)
    spend_bundle = SpendBundle.aggregate(
        [launcher_spend_bundle,
         SpendBundle([coin_solution], G2Element())])
    run = asyncio.get_event_loop().run_until_complete
    coins_added, coins_removed = run(
        check_spend_bundle_validity(bt.constants, blocks, spend_bundle))

    coin_set_added = set([_.coin for _ in coins_added])
    coin_set_removed = set([_.coin for _ in coins_removed])

    launcher_coin = launcher_spend_bundle.coin_solutions[0].coin

    assert launcher_coin in coin_set_added
    assert launcher_coin in coin_set_removed

    assert farmed_coin in coin_set_removed
    # breakpoint()

    singleton_expected_puzzle_hash = singleton_puzzle_hash(
        launcher_id, launcher_puzzle_hash, initial_singleton_puzzle)
    expected_singleton_coin = Coin(launcher_coin.name(),
                                   singleton_expected_puzzle_hash,
                                   launcher_amount)
    assert expected_singleton_coin in coin_set_added

    # next up: spend the expected_singleton_coin
    # it's an adapted `ANYONE_CAN_SPEND_PUZZLE`

    # then try a bad lineage proof
    # then try writing two odd coins
    # then try writing zero odd coins

    # then, destroy the singleton with the -113 hack

    return 0
def spend_coin_to_singleton(
        puzzle_db: PuzzleDB, launcher_puzzle: Program, coin_store: CoinStore,
        now: CoinTimestamp) -> Tuple[List[Coin], List[CoinSpend]]:

    farmed_coin_amount = 100000
    metadata = [("foo", "bar")]

    now = CoinTimestamp(10012300, 1)
    farmed_coin = coin_store.farm_coin(ANYONE_CAN_SPEND_PUZZLE.get_tree_hash(),
                                       now,
                                       amount=farmed_coin_amount)
    now.seconds += 500
    now.height += 1

    launcher_amount: uint64 = uint64(1)
    launcher_puzzle = LAUNCHER_PUZZLE
    launcher_puzzle_hash = launcher_puzzle.get_tree_hash()
    initial_singleton_puzzle = adaptor_for_singleton_inner_puzzle(
        ANYONE_CAN_SPEND_PUZZLE)
    launcher_id, condition_list, launcher_spend_bundle = launcher_conditions_and_spend_bundle(
        puzzle_db, farmed_coin.name(), launcher_amount,
        initial_singleton_puzzle, metadata, launcher_puzzle)

    conditions = Program.to(condition_list)
    coin_spend = CoinSpend(farmed_coin, ANYONE_CAN_SPEND_PUZZLE, conditions)
    spend_bundle = SpendBundle.aggregate(
        [launcher_spend_bundle,
         SpendBundle([coin_spend], G2Element())])

    additions, removals = coin_store.update_coin_store_for_spend_bundle(
        spend_bundle, now, MAX_BLOCK_COST_CLVM, COST_PER_BYTE)

    launcher_coin = launcher_spend_bundle.coin_spends[0].coin

    assert_coin_spent(coin_store, launcher_coin)
    assert_coin_spent(coin_store, farmed_coin)

    # TODO: address hint error and remove ignore
    #       error: Argument 1 to "singleton_puzzle" has incompatible type "bytes32"; expected "Program"  [arg-type]
    singleton_expected_puzzle = singleton_puzzle(
        launcher_id,  # type: ignore[arg-type]
        launcher_puzzle_hash,
        initial_singleton_puzzle,
    )
    singleton_expected_puzzle_hash = singleton_expected_puzzle.get_tree_hash()
    expected_singleton_coin = Coin(launcher_coin.name(),
                                   singleton_expected_puzzle_hash,
                                   launcher_amount)
    assert_coin_spent(coin_store, expected_singleton_coin, is_spent=False)

    return additions, removals
    async def test_invalid_announcement_consumed_two(self, two_nodes):
        reward_ph = WALLET_A.get_new_puzzlehash()
        full_node_1, full_node_2, server_1, server_2 = two_nodes
        blocks = await full_node_1.get_all_full_blocks()
        start_height = blocks[-1].height if len(blocks) > 0 else -1
        blocks = bt.get_consecutive_blocks(
            3,
            block_list_input=blocks,
            guarantee_transaction_block=True,
            farmer_reward_puzzle_hash=reward_ph,
            pool_reward_puzzle_hash=reward_ph,
        )
        peer = await connect_and_get_peer(server_1, server_2)

        for block in blocks:
            await full_node_1.full_node.respond_block(
                full_node_protocol.RespondBlock(block))

        await time_out_assert(60, node_height_at_least, True, full_node_1,
                              start_height + 3)

        coin_1 = list(blocks[-2].get_included_reward_coins())[0]
        coin_2 = list(blocks[-1].get_included_reward_coins())[0]

        announce = Announcement(coin_1.name(), bytes("test", "utf-8"))

        cvp = ConditionVarPair(ConditionOpcode.ASSERT_ANNOUNCEMENT,
                               [announce.name()])

        dic = {cvp.opcode: [cvp]}

        cvp2 = ConditionVarPair(
            ConditionOpcode.CREATE_ANNOUNCEMENT,
            [bytes("test", "utf-8")],
        )
        dic2 = {cvp.opcode: [cvp2]}
        spend_bundle1 = generate_test_spend_bundle(coin_1, dic)

        spend_bundle2 = generate_test_spend_bundle(coin_2, dic2)

        bundle = SpendBundle.aggregate([spend_bundle1, spend_bundle2])

        tx1: full_node_protocol.RespondTransaction = full_node_protocol.RespondTransaction(
            spend_bundle1)
        await full_node_1.respond_transaction(tx1, peer)

        mempool_bundle = full_node_1.full_node.mempool_manager.get_spendbundle(
            bundle.name())

        assert mempool_bundle is None
    async def create_bundle_from_mempool(
        self, last_tb_header_hash: bytes32
    ) -> Optional[Tuple[SpendBundle, List[Coin], List[Coin]]]:
        """
        Returns aggregated spendbundle that can be used for creating new block,
        additions and removals in that spend_bundle
        """
        if (self.peak is None or self.peak.header_hash != last_tb_header_hash
                or int(time.time()) <=
                self.constants.INITIAL_FREEZE_END_TIMESTAMP):
            return None

        cost_sum = 0  # Checks that total cost does not exceed block maximum
        fee_sum = 0  # Checks that total fees don't exceed 64 bits
        spend_bundles: List[SpendBundle] = []
        removals = []
        additions = []
        broke_from_inner_loop = False
        log.info(
            f"Starting to make block, max cost: {self.constants.MAX_BLOCK_COST_CLVM}"
        )
        for dic in reversed(self.mempool.sorted_spends.values()):
            if broke_from_inner_loop:
                break
            for item in dic.values():
                log.info(
                    f"Cumulative cost: {cost_sum}, fee per cost: {item.fee / item.cost}"
                )
                if (item.cost + cost_sum <=
                        self.limit_factor * self.constants.MAX_BLOCK_COST_CLVM
                        and
                        item.fee + fee_sum <= self.constants.MAX_COIN_AMOUNT):
                    spend_bundles.append(item.spend_bundle)
                    cost_sum += item.cost
                    fee_sum += item.fee
                    removals.extend(item.removals)
                    additions.extend(item.additions)
                else:
                    broke_from_inner_loop = True
                    break
        if len(spend_bundles) > 0:
            log.info(
                f"Cumulative cost of block (real cost should be less) {cost_sum}. Proportion "
                f"full: {cost_sum / self.constants.MAX_BLOCK_COST_CLVM}")
            agg = SpendBundle.aggregate(spend_bundles)
            assert set(agg.additions()) == set(additions)
            assert set(agg.removals()) == set(removals)
            return agg, additions, removals
        else:
            return None
Beispiel #14
0
        def test_fun(coin_1: Coin, coin_2: Coin):
            announce = Announcement(coin_2.puzzle_hash, bytes(0x80))

            cvp = ConditionWithArgs(ConditionOpcode.ASSERT_PUZZLE_ANNOUNCEMENT, [announce.name()])

            dic = {cvp.opcode: [cvp]}

            cvp2 = ConditionWithArgs(ConditionOpcode.CREATE_PUZZLE_ANNOUNCEMENT, [bytes(0x80)])
            dic2 = {cvp.opcode: [cvp2]}
            spend_bundle1 = generate_test_spend_bundle(coin_1, dic)

            spend_bundle2 = generate_test_spend_bundle(coin_2, dic2)

            return SpendBundle.aggregate([spend_bundle1, spend_bundle2])
Beispiel #15
0
        def test_fun(coin_1: Coin, coin_2: Coin):
            announce = Announcement(coin_1.name(), b"test")

            cvp = ConditionWithArgs(ConditionOpcode.ASSERT_COIN_ANNOUNCEMENT, [announce.name()])

            dic = {cvp.opcode: [cvp]}

            cvp2 = ConditionWithArgs(
                ConditionOpcode.CREATE_COIN_ANNOUNCEMENT,
                [b"test"],
            )
            dic2 = {cvp.opcode: [cvp2]}
            spend_bundle1 = generate_test_spend_bundle(coin_1, dic)

            # coin 2 is making the announcement, right message wrong coin
            spend_bundle2 = generate_test_spend_bundle(coin_2, dic2)

            return SpendBundle.aggregate([spend_bundle1, spend_bundle2])
Beispiel #16
0
    async def test_validate_blockchain_duplicate_output(self, two_nodes):
        num_blocks = 3
        wallet_a = WALLET_A
        coinbase_puzzlehash = WALLET_A_PUZZLE_HASHES[0]
        receiver_puzzlehash = BURN_PUZZLE_HASH

        blocks = bt.get_consecutive_blocks(
            num_blocks,
            farmer_reward_puzzle_hash=coinbase_puzzlehash,
            guarantee_transaction_block=True)
        full_node_api_1, full_node_api_2, server_1, server_2 = two_nodes
        full_node_1 = full_node_api_1.full_node

        for block in blocks:
            await full_node_api_1.full_node.respond_block(
                full_node_protocol.RespondBlock(block))

        spend_block = blocks[2]

        spend_coin = None
        for coin in list(spend_block.get_included_reward_coins()):
            if coin.puzzle_hash == coinbase_puzzlehash:
                spend_coin = coin

        spend_bundle = wallet_a.generate_signed_transaction(
            1000, receiver_puzzlehash, spend_coin)
        spend_bundle_double = wallet_a.generate_signed_transaction(
            1000, receiver_puzzlehash, spend_coin)

        block_spendbundle = SpendBundle.aggregate(
            [spend_bundle, spend_bundle_double])

        new_blocks = bt.get_consecutive_blocks(
            1,
            block_list_input=blocks,
            farmer_reward_puzzle_hash=coinbase_puzzlehash,
            transaction_data=block_spendbundle,
            guarantee_transaction_block=True,
        )

        next_block = new_blocks[-1]
        res, err, _ = await full_node_1.blockchain.receive_block(next_block)
        assert res == ReceiveBlockResult.INVALID_BLOCK
        assert err == Err.DUPLICATE_OUTPUT
    async def test_double_spend_same_bundle(self, two_nodes):
        reward_ph = WALLET_A.get_new_puzzlehash()
        full_node_1, full_node_2, server_1, server_2 = two_nodes
        blocks = await full_node_1.get_all_full_blocks()
        start_height = blocks[-1].height
        blocks = bt.get_consecutive_blocks(
            3,
            block_list_input=blocks,
            guarantee_transaction_block=True,
            farmer_reward_puzzle_hash=reward_ph,
            pool_reward_puzzle_hash=reward_ph,
        )
        peer = await connect_and_get_peer(server_1, server_2)

        for block in blocks:
            await full_node_1.full_node.respond_block(
                full_node_protocol.RespondBlock(block))

        await time_out_assert(60, node_height_at_least, True, full_node_1,
                              start_height + 3)
        coin = list(blocks[-1].get_included_reward_coins())[0]
        spend_bundle1 = generate_test_spend_bundle(coin)

        assert spend_bundle1 is not None

        spend_bundle2 = generate_test_spend_bundle(
            coin,
            new_puzzle_hash=BURN_PUZZLE_HASH_2,
        )

        assert spend_bundle2 is not None

        spend_bundle_combined = SpendBundle.aggregate(
            [spend_bundle1, spend_bundle2])

        tx: full_node_protocol.RespondTransaction = full_node_protocol.RespondTransaction(
            spend_bundle_combined)

        await full_node_1.respond_transaction(tx, peer)

        sb = full_node_1.full_node.mempool_manager.get_spendbundle(
            spend_bundle_combined.name())
        assert sb is None
Beispiel #18
0
    async def create_bundle_from_mempool(
        self, peak_header_hash: bytes32
    ) -> Optional[Tuple[SpendBundle, List[Coin], List[Coin]]]:
        """
        Returns aggregated spendbundle that can be used for creating new block,
        additions and removals in that spend_bundle
        """
        if (
            self.peak is None
            or self.peak.header_hash != peak_header_hash
            or self.peak.height <= self.constants.INITIAL_FREEZE_PERIOD
        ):
            return None

        cost_sum = 0  # Checks that total cost does not exceed block maximum
        fee_sum = 0  # Checks that total fees don't exceed 64 bits
        spend_bundles: List[SpendBundle] = []
        removals = []
        additions = []
        broke_from_inner_loop = False
        log.info(f"Starting to make block, max cost: {self.constants.MAX_BLOCK_COST_CLVM}")
        for dic in self.mempool.sorted_spends.values():
            if broke_from_inner_loop:
                break
            for item in dic.values():
                log.info(f"Cumulative cost: {cost_sum}")
                if (
                    item.cost_result.cost + cost_sum <= self.constants.MAX_BLOCK_COST_CLVM
                    and item.fee + fee_sum <= self.constants.MAX_COIN_AMOUNT
                ):
                    spend_bundles.append(item.spend_bundle)
                    cost_sum += item.cost_result.cost
                    fee_sum += item.fee
                    removals.extend(item.removals)
                    additions.extend(item.additions)
                else:
                    broke_from_inner_loop = True
                    break
        if len(spend_bundles) > 0:
            return SpendBundle.aggregate(spend_bundles), additions, removals
        else:
            return None
    async def make_and_spend_piggybank(self, network, alice, bob,
                                       CONTRIBUTION_AMOUNT):
        # Get our alice wallet some money
        await network.farm_block(farmer=alice)

        # This will use one mojo to create our piggybank on the blockchain.
        piggybank_coin = await alice.launch_smart_coin(
            create_piggybank_puzzle(1000000000000, bob.puzzle_hash))
        # This retrieves us a coin that is at least 500 mojos.
        contribution_coin = await alice.choose_coin(CONTRIBUTION_AMOUNT)

        #This is the spend of the piggy bank coin.  We use the driver code to create the solution.
        piggybank_spend = await alice.spend_coin(
            piggybank_coin,
            pushtx=False,
            args=solution_for_piggybank(piggybank_coin.as_coin(),
                                        CONTRIBUTION_AMOUNT),
        )
        # This is the spend of a standard coin.  We simply spend to ourselves but minus the CONTRIBUTION_AMOUNT.
        contribution_spend = await alice.spend_coin(
            contribution_coin,
            pushtx=False,
            amt=(contribution_coin.amount - CONTRIBUTION_AMOUNT),
            custom_conditions=[[
                ConditionOpcode.CREATE_COIN, contribution_coin.puzzle_hash,
                (contribution_coin.amount - CONTRIBUTION_AMOUNT)
            ],
                               piggybank_announcement_assertion(
                                   piggybank_coin.as_coin(),
                                   CONTRIBUTION_AMOUNT)])

        # Aggregate them to make sure they are spent together
        combined_spend = SpendBundle.aggregate(
            [contribution_spend, piggybank_spend])

        result = await network.push_tx(combined_spend)
        return result
Beispiel #20
0
    async def create_absorb_transaction(
            self, farmer_record: FarmerRecord, singleton_coin: Coin,
            reward_coin_records: List[CoinRecord]) -> SpendBundle:
        # We assume that the farmer record singleton state is updated to the latest
        escape_inner_puzzle: Program = POOL_ESCAPING_MOD.curry(
            farmer_record.pool_puzzle_hash,
            self.relative_lock_height,
            bytes(farmer_record.owner_public_key),
            farmer_record.p2_singleton_puzzle_hash,
        )
        committed_inner_puzzle: Program = POOL_COMMITED_MOD.curry(
            farmer_record.pool_puzzle_hash,
            escape_inner_puzzle.get_tree_hash(),
            farmer_record.p2_singleton_puzzle_hash,
            bytes(farmer_record.owner_public_key),
        )

        aggregate_spend_bundle: SpendBundle = SpendBundle([], G2Element())
        for reward_coin_record in reward_coin_records:
            found_block_index: Optional[uint32] = None
            for block_index in range(
                    reward_coin_record.confirmed_block_index,
                    reward_coin_record.confirmed_block_index - 100, -1):
                if block_index < 0:
                    break
                pool_parent = pool_parent_id(uint32(block_index),
                                             self.constants.GENESIS_CHALLENGE)
                if pool_parent == reward_coin_record.coin.parent_coin_info:
                    found_block_index = uint32(block_index)
            if not found_block_index:
                self.log.info(
                    f"Received reward {reward_coin_record.coin} that is not a pool reward."
                )

            singleton_full = SINGLETON_MOD.curry(
                singleton_mod_hash, farmer_record.singleton_genesis,
                committed_inner_puzzle)

            inner_sol = Program.to([
                0,
                singleton_full.get_tree_hash(), singleton_coin.amount,
                reward_coin_record.amount, found_block_index
            ])
            full_sol = Program.to([
                farmer_record.singleton_genesis, singleton_coin.amount,
                inner_sol
            ])

            new_spend = SpendBundle(
                [
                    CoinSolution(
                        singleton_coin,
                        SerializedProgram.from_bytes(bytes(singleton_full)),
                        full_sol)
                ],
                G2Element(),
            )
            # TODO(pool): handle the case where the cost exceeds the size of the block
            aggregate_spend_bundle = SpendBundle.aggregate(
                [aggregate_spend_bundle, new_spend])

            singleton_coin = await self.get_next_singleton_coin(new_spend)

            cost, result = singleton_full.run_with_cost(
                INFINITE_COST, full_sol)
            self.log.info(f"Cost: {cost}, result {result}")

        return aggregate_spend_bundle
Beispiel #21
0
    async def test_double_spend_with_higher_fee(self, two_nodes):
        reward_ph = WALLET_A.get_new_puzzlehash()

        full_node_1, full_node_2, server_1, server_2 = two_nodes
        blocks = await full_node_1.get_all_full_blocks()
        start_height = blocks[-1].height
        blocks = bt.get_consecutive_blocks(
            3,
            block_list_input=blocks,
            guarantee_transaction_block=True,
            farmer_reward_puzzle_hash=reward_ph,
            pool_reward_puzzle_hash=reward_ph,
        )
        peer = await connect_and_get_peer(server_1, server_2)

        for block in blocks:
            await full_node_1.full_node.respond_block(full_node_protocol.RespondBlock(block))
        await time_out_assert(60, node_height_at_least, True, full_node_1, start_height + 3)

        coins = iter(blocks[-1].get_included_reward_coins())
        coin1, coin2 = next(coins), next(coins)
        coins = iter(blocks[-2].get_included_reward_coins())
        coin3, coin4 = next(coins), next(coins)

        sb1_1 = await self.gen_and_send_sb(full_node_1, peer, coin1)
        sb1_2 = await self.gen_and_send_sb(full_node_1, peer, coin1, fee=uint64(1))

        # Fee increase is insufficient, the old spendbundle must stay
        self.assert_sb_in_pool(full_node_1, sb1_1)
        self.assert_sb_not_in_pool(full_node_1, sb1_2)

        min_fee_increase = full_node_1.full_node.mempool_manager.get_min_fee_increase()

        sb1_3 = await self.gen_and_send_sb(full_node_1, peer, coin1, fee=uint64(min_fee_increase))

        # Fee increase is sufficiently high, sb1_1 gets replaced with sb1_3
        self.assert_sb_not_in_pool(full_node_1, sb1_1)
        self.assert_sb_in_pool(full_node_1, sb1_3)

        sb2 = generate_test_spend_bundle(coin2, fee=uint64(min_fee_increase))
        sb12 = SpendBundle.aggregate((sb2, sb1_3))
        await self.send_sb(full_node_1, peer, sb12)

        # Aggregated spendbundle sb12 replaces sb1_3 since it spends a superset
        # of coins spent in sb1_3
        self.assert_sb_in_pool(full_node_1, sb12)
        self.assert_sb_not_in_pool(full_node_1, sb1_3)

        sb3 = generate_test_spend_bundle(coin3, fee=uint64(min_fee_increase * 2))
        sb23 = SpendBundle.aggregate((sb2, sb3))
        await self.send_sb(full_node_1, peer, sb23)

        # sb23 must not replace existing sb12 as the former does not spend all
        # coins that are spent in the latter (specifically, coin1)
        self.assert_sb_in_pool(full_node_1, sb12)
        self.assert_sb_not_in_pool(full_node_1, sb23)

        await self.send_sb(full_node_1, peer, sb3)
        # Adding non-conflicting sb3 should succeed
        self.assert_sb_in_pool(full_node_1, sb3)

        sb4_1 = generate_test_spend_bundle(coin4, fee=uint64(min_fee_increase))
        sb1234_1 = SpendBundle.aggregate((sb12, sb3, sb4_1))
        await self.send_sb(full_node_1, peer, sb1234_1)
        # sb1234_1 should not be in pool as it decreases total fees per cost
        self.assert_sb_not_in_pool(full_node_1, sb1234_1)

        sb4_2 = generate_test_spend_bundle(coin4, fee=uint64(min_fee_increase * 2))
        sb1234_2 = SpendBundle.aggregate((sb12, sb3, sb4_2))
        await self.send_sb(full_node_1, peer, sb1234_2)
        # sb1234_2 has a higher fee per cost than its conflicts and should get
        # into mempool
        self.assert_sb_in_pool(full_node_1, sb1234_2)
        self.assert_sb_not_in_pool(full_node_1, sb12)
        self.assert_sb_not_in_pool(full_node_1, sb3)
Beispiel #22
0
    async def generate_launcher_spend(
        standard_wallet: Wallet,
        amount: uint64,
        initial_target_state: PoolState,
        genesis_challenge: bytes32,
        delay_time: uint64,
        delay_ph: bytes32,
    ) -> Tuple[SpendBundle, bytes32, bytes32]:
        """
        Creates the initial singleton, which includes spending an origin coin, the launcher, and creating a singleton
        with the "pooling" inner state, which can be either self pooling or using a pool
        """
        coins: Set[Coin] = await standard_wallet.select_coins(amount)
        if coins is None:
            raise ValueError("Not enough coins to create pool wallet")

        assert len(coins) == 1

        launcher_parent: Coin = coins.copy().pop()
        genesis_launcher_puz: Program = SINGLETON_LAUNCHER
        launcher_coin: Coin = Coin(launcher_parent.name(), genesis_launcher_puz.get_tree_hash(), amount)

        escaping_inner_puzzle: Program = create_waiting_room_inner_puzzle(
            initial_target_state.target_puzzle_hash,
            initial_target_state.relative_lock_height,
            initial_target_state.owner_pubkey,
            launcher_coin.name(),
            genesis_challenge,
            delay_time,
            delay_ph,
        )
        escaping_inner_puzzle_hash = escaping_inner_puzzle.get_tree_hash()

        self_pooling_inner_puzzle: Program = create_pooling_inner_puzzle(
            initial_target_state.target_puzzle_hash,
            escaping_inner_puzzle_hash,
            initial_target_state.owner_pubkey,
            launcher_coin.name(),
            genesis_challenge,
            delay_time,
            delay_ph,
        )

        if initial_target_state.state == SELF_POOLING:
            puzzle = escaping_inner_puzzle
        elif initial_target_state.state == FARMING_TO_POOL:
            puzzle = self_pooling_inner_puzzle
        else:
            raise ValueError("Invalid initial state")
        full_pooling_puzzle: Program = create_full_puzzle(puzzle, launcher_id=launcher_coin.name())

        puzzle_hash: bytes32 = full_pooling_puzzle.get_tree_hash()
        pool_state_bytes = Program.to([("p", bytes(initial_target_state)), ("t", delay_time), ("h", delay_ph)])
        announcement_set: Set[Announcement] = set()
        announcement_message = Program.to([puzzle_hash, amount, pool_state_bytes]).get_tree_hash()
        announcement_set.add(Announcement(launcher_coin.name(), announcement_message))

        create_launcher_tx_record: Optional[TransactionRecord] = await standard_wallet.generate_signed_transaction(
            amount,
            genesis_launcher_puz.get_tree_hash(),
            uint64(0),
            None,
            coins,
            None,
            False,
            announcement_set,
        )
        assert create_launcher_tx_record is not None and create_launcher_tx_record.spend_bundle is not None

        genesis_launcher_solution: Program = Program.to([puzzle_hash, amount, pool_state_bytes])

        launcher_cs: CoinSpend = CoinSpend(
            launcher_coin,
            SerializedProgram.from_program(genesis_launcher_puz),
            SerializedProgram.from_program(genesis_launcher_solution),
        )
        launcher_sb: SpendBundle = SpendBundle([launcher_cs], G2Element())

        # Current inner will be updated when state is verified on the blockchain
        full_spend: SpendBundle = SpendBundle.aggregate([create_launcher_tx_record.spend_bundle, launcher_sb])
        return full_spend, puzzle_hash, launcher_coin.name()
Beispiel #23
0
    async def recovery_spend(
        self,
        coin: Coin,
        puzhash: bytes,
        parent_innerpuzhash_amounts_for_recovery_ids: List[Tuple[bytes, bytes,
                                                                 int]],
        pubkey: G1Element,
        spend_bundle: SpendBundle,
    ) -> SpendBundle:
        assert self.did_info.origin_coin is not None
        # innerpuz solution is (mode amount new_puz identity my_puz parent_innerpuzhash_amounts_for_recovery_ids)
        innersol = Program.to([
            2,
            coin.amount,
            puzhash,
            coin.name(),
            coin.puzzle_hash,
            parent_innerpuzhash_amounts_for_recovery_ids,
            bytes(pubkey),
            self.did_info.backup_ids,
            self.did_info.num_of_backup_ids_needed,
        ])
        # full solution is (parent_info my_amount solution)
        innerpuz = self.did_info.current_inner
        full_puzzle: Program = did_wallet_puzzles.create_fullpuz(
            innerpuz,
            self.did_info.origin_coin.puzzle_hash,
        )
        parent_info = await self.get_parent_for_coin(coin)
        assert parent_info is not None
        fullsol = Program.to([
            [
                self.did_info.origin_coin.parent_coin_info,
                self.did_info.origin_coin.amount
            ],
            [
                parent_info.parent_name,
                parent_info.inner_puzzle_hash,
                parent_info.amount,
            ],
            coin.amount,
            innersol,
        ])
        list_of_solutions = [CoinSolution(coin, full_puzzle, fullsol)]

        index = await self.wallet_state_manager.puzzle_store.index_for_pubkey(
            pubkey)
        if index is None:
            raise ValueError("Unknown pubkey.")
        private = master_sk_to_wallet_sk(self.wallet_state_manager.private_key,
                                         index)
        message = bytes(puzhash)
        sigs = [AugSchemeMPL.sign(private, message)]
        for _ in spend_bundle.coin_solutions:
            sigs.append(AugSchemeMPL.sign(private, message))
        aggsig = AugSchemeMPL.aggregate(sigs)
        # assert AugSchemeMPL.verify(pubkey, message, aggsig)
        if spend_bundle is None:
            spend_bundle = SpendBundle(list_of_solutions, aggsig)
        else:
            spend_bundle = spend_bundle.aggregate(
                [spend_bundle,
                 SpendBundle(list_of_solutions, aggsig)])

        did_record = TransactionRecord(
            confirmed_at_height=uint32(0),
            created_at_time=uint64(int(time.time())),
            to_puzzle_hash=puzhash,
            amount=uint64(coin.amount),
            fee_amount=uint64(0),
            confirmed=False,
            sent=uint32(0),
            spend_bundle=spend_bundle,
            additions=spend_bundle.additions(),
            removals=spend_bundle.removals(),
            wallet_id=self.wallet_info.id,
            sent_to=[],
            trade_id=None,
            type=uint32(TransactionType.OUTGOING_TX.value),
            name=token_bytes(),
        )
        await self.standard_wallet.push_transaction(did_record)
        return spend_bundle
Beispiel #24
0
    async def generate_unsigned_spendbundle(
        self,
        payments: List[Payment],
        fee: uint64 = uint64(0),
        cat_discrepancy: Optional[Tuple[
            int, Program]] = None,  # (extra_delta, limitations_solution)
        coins: Set[Coin] = None,
        coin_announcements_to_consume: Optional[Set[Announcement]] = None,
        puzzle_announcements_to_consume: Optional[Set[Announcement]] = None,
    ) -> Tuple[SpendBundle, Optional[TransactionRecord]]:
        if coin_announcements_to_consume is not None:
            coin_announcements_bytes: Optional[Set[bytes32]] = {
                a.name()
                for a in coin_announcements_to_consume
            }
        else:
            coin_announcements_bytes = None

        if puzzle_announcements_to_consume is not None:
            puzzle_announcements_bytes: Optional[Set[bytes32]] = {
                a.name()
                for a in puzzle_announcements_to_consume
            }
        else:
            puzzle_announcements_bytes = None

        if cat_discrepancy is not None:
            extra_delta, limitations_solution = cat_discrepancy
        else:
            extra_delta, limitations_solution = 0, Program.to([])
        payment_amount: int = sum([p.amount for p in payments])
        starting_amount: int = payment_amount - extra_delta

        if coins is None:
            cat_coins = await self.select_coins(uint64(starting_amount))
        else:
            cat_coins = coins

        selected_cat_amount = sum([c.amount for c in cat_coins])
        assert selected_cat_amount >= starting_amount

        # Figure out if we need to absorb/melt some XCH as part of this
        regular_chia_to_claim: int = 0
        if payment_amount > starting_amount:
            fee = uint64(fee + payment_amount - starting_amount)
        elif payment_amount < starting_amount:
            regular_chia_to_claim = payment_amount

        need_chia_transaction = (fee > 0 or regular_chia_to_claim > 0) and (
            fee - regular_chia_to_claim != 0)

        # Calculate standard puzzle solutions
        change = selected_cat_amount - starting_amount
        primaries: List[AmountWithPuzzlehash] = []
        for payment in payments:
            primaries.append({
                "puzzlehash": payment.puzzle_hash,
                "amount": payment.amount,
                "memos": payment.memos
            })

        if change > 0:
            changepuzzlehash = await self.get_new_inner_hash()
            primaries.append({
                "puzzlehash": changepuzzlehash,
                "amount": uint64(change),
                "memos": []
            })

        limitations_program_reveal = Program.to([])
        if self.cat_info.my_tail is None:
            assert cat_discrepancy is None
        elif cat_discrepancy is not None:
            limitations_program_reveal = self.cat_info.my_tail

        # Loop through the coins we've selected and gather the information we need to spend them
        spendable_cc_list = []
        chia_tx = None
        first = True
        for coin in cat_coins:
            if first:
                first = False
                if need_chia_transaction:
                    if fee > regular_chia_to_claim:
                        announcement = Announcement(coin.name(), b"$", b"\xca")
                        chia_tx, _ = await self.create_tandem_xch_tx(
                            fee,
                            uint64(regular_chia_to_claim),
                            announcement_to_assert=announcement)
                        innersol = self.standard_wallet.make_solution(
                            primaries=primaries,
                            coin_announcements={announcement.message},
                            coin_announcements_to_assert=
                            coin_announcements_bytes,
                            puzzle_announcements_to_assert=
                            puzzle_announcements_bytes,
                        )
                    elif regular_chia_to_claim > fee:
                        chia_tx, _ = await self.create_tandem_xch_tx(
                            fee, uint64(regular_chia_to_claim))
                        innersol = self.standard_wallet.make_solution(
                            primaries=primaries,
                            coin_announcements_to_assert={announcement.name()})
                else:
                    innersol = self.standard_wallet.make_solution(
                        primaries=primaries,
                        coin_announcements_to_assert=coin_announcements_bytes,
                        puzzle_announcements_to_assert=
                        puzzle_announcements_bytes,
                    )
            else:
                innersol = self.standard_wallet.make_solution(primaries=[])
            inner_puzzle = await self.inner_puzzle_for_cc_puzhash(
                coin.puzzle_hash)
            lineage_proof = await self.get_lineage_proof_for_coin(coin)
            assert lineage_proof is not None
            new_spendable_cc = SpendableCAT(
                coin,
                self.cat_info.limitations_program_hash,
                inner_puzzle,
                innersol,
                limitations_solution=limitations_solution,
                extra_delta=extra_delta,
                lineage_proof=lineage_proof,
                limitations_program_reveal=limitations_program_reveal,
            )
            spendable_cc_list.append(new_spendable_cc)

        cat_spend_bundle = unsigned_spend_bundle_for_spendable_cats(
            CAT_MOD, spendable_cc_list)
        chia_spend_bundle = SpendBundle([], G2Element())
        if chia_tx is not None and chia_tx.spend_bundle is not None:
            chia_spend_bundle = chia_tx.spend_bundle

        return (
            SpendBundle.aggregate([
                cat_spend_bundle,
                chia_spend_bundle,
            ]),
            chia_tx,
        )
Beispiel #25
0
    async def test_assert_announcement_consumed(self, two_nodes):

        num_blocks = 10
        wallet_a = WALLET_A
        coinbase_puzzlehash = WALLET_A_PUZZLE_HASHES[0]
        receiver_puzzlehash = BURN_PUZZLE_HASH

        # Farm blocks
        blocks = bt.get_consecutive_blocks(
            num_blocks,
            farmer_reward_puzzle_hash=coinbase_puzzlehash,
            guarantee_transaction_block=True)
        full_node_api_1, full_node_api_2, server_1, server_2 = two_nodes
        full_node_1 = full_node_api_1.full_node

        for block in blocks:
            await full_node_api_1.full_node.respond_block(
                full_node_protocol.RespondBlock(block))

        # Coinbase that gets spent
        block1 = blocks[2]
        block2 = blocks[3]

        spend_coin_block_1 = None
        spend_coin_block_2 = None
        for coin in list(block1.get_included_reward_coins()):
            if coin.puzzle_hash == coinbase_puzzlehash:
                spend_coin_block_1 = coin
        for coin in list(block2.get_included_reward_coins()):
            if coin.puzzle_hash == coinbase_puzzlehash:
                spend_coin_block_2 = coin

        # This condition requires block2 coinbase to be spent
        block1_cvp = ConditionWithArgs(
            ConditionOpcode.ASSERT_ANNOUNCEMENT,
            [
                Announcement(spend_coin_block_2.name(), bytes("test",
                                                              "utf-8")).name()
            ],
        )
        block1_dic = {block1_cvp.opcode: [block1_cvp]}
        block1_spend_bundle = wallet_a.generate_signed_transaction(
            1000, receiver_puzzlehash, spend_coin_block_1, block1_dic)

        # This condition requires block1 coinbase to be spent
        block2_cvp = ConditionWithArgs(
            ConditionOpcode.CREATE_ANNOUNCEMENT,
            [bytes("test", "utf-8")],
        )
        block2_dic = {block2_cvp.opcode: [block2_cvp]}
        block2_spend_bundle = wallet_a.generate_signed_transaction(
            1000, receiver_puzzlehash, spend_coin_block_2, block2_dic)

        # Invalid block bundle
        assert block1_spend_bundle is not None
        # Create another block that includes our transaction
        invalid_new_blocks = bt.get_consecutive_blocks(
            1,
            blocks,
            farmer_reward_puzzle_hash=coinbase_puzzlehash,
            transaction_data=block1_spend_bundle,
            guarantee_transaction_block=True,
        )

        # Try to validate that block
        res, err, _ = await full_node_1.blockchain.receive_block(
            invalid_new_blocks[-1])
        assert res == ReceiveBlockResult.INVALID_BLOCK
        assert err == Err.ASSERT_ANNOUNCE_CONSUMED_FAILED

        # bundle_together contains both transactions
        bundle_together = SpendBundle.aggregate(
            [block1_spend_bundle, block2_spend_bundle])

        # Create another block that includes our transaction
        new_blocks = bt.get_consecutive_blocks(
            1,
            blocks,
            farmer_reward_puzzle_hash=coinbase_puzzlehash,
            transaction_data=bundle_together,
            guarantee_transaction_block=True,
        )

        # Try to validate newly created block
        res, err, _ = await full_node_1.blockchain.receive_block(new_blocks[-1]
                                                                 )
        assert res == ReceiveBlockResult.NEW_PEAK
        assert err is None
Beispiel #26
0
    async def generate_new_decentralised_id(
            self, amount: uint64) -> Optional[SpendBundle]:
        """
        This must be called under the wallet state manager lock
        """

        coins = await self.standard_wallet.select_coins(amount)
        if coins is None:
            return None

        origin = coins.copy().pop()
        genesis_launcher_puz = did_wallet_puzzles.SINGLETON_LAUNCHER
        launcher_coin = Coin(origin.name(),
                             genesis_launcher_puz.get_tree_hash(), amount)

        did_inner: Program = await self.get_new_innerpuz()
        did_inner_hash = did_inner.get_tree_hash()
        did_full_puz = did_wallet_puzzles.create_fullpuz(
            did_inner, launcher_coin.name())
        did_puzzle_hash = did_full_puz.get_tree_hash()

        announcement_set: Set[Announcement] = set()
        announcement_message = Program.to(
            [did_puzzle_hash, amount, bytes(0x80)]).get_tree_hash()
        announcement_set.add(
            Announcement(launcher_coin.name(), announcement_message).name())

        tx_record: Optional[
            TransactionRecord] = await self.standard_wallet.generate_signed_transaction(
                amount, genesis_launcher_puz.get_tree_hash(), uint64(0),
                origin.name(), coins, None, False, announcement_set)

        genesis_launcher_solution = Program.to(
            [did_puzzle_hash, amount, bytes(0x80)])

        launcher_cs = CoinSolution(launcher_coin, genesis_launcher_puz,
                                   genesis_launcher_solution)
        launcher_sb = SpendBundle([launcher_cs], AugSchemeMPL.aggregate([]))
        eve_coin = Coin(launcher_coin.name(), did_puzzle_hash, amount)
        future_parent = LineageProof(
            eve_coin.parent_coin_info,
            did_inner_hash,
            eve_coin.amount,
        )
        eve_parent = LineageProof(
            launcher_coin.parent_coin_info,
            launcher_coin.puzzle_hash,
            launcher_coin.amount,
        )
        await self.add_parent(eve_coin.parent_coin_info, eve_parent, False)
        await self.add_parent(eve_coin.name(), future_parent, False)

        if tx_record is None or tx_record.spend_bundle is None:
            return None

        # Only want to save this information if the transaction is valid
        did_info: DIDInfo = DIDInfo(
            launcher_coin,
            self.did_info.backup_ids,
            self.did_info.num_of_backup_ids_needed,
            self.did_info.parent_info,
            did_inner,
            None,
            None,
            None,
        )
        await self.save_info(did_info, False)
        eve_spend = await self.generate_eve_spend(eve_coin, did_full_puz,
                                                  did_inner)
        full_spend = SpendBundle.aggregate(
            [tx_record.spend_bundle, eve_spend, launcher_sb])
        return full_spend
    async def respond_to_offer(
            self, file_path: Path
    ) -> Tuple[bool, Optional[TradeRecord], Optional[str]]:
        has_wallets = await self.maybe_create_wallets_for_offer(file_path)
        if not has_wallets:
            return False, None, "Unknown Error"
        trade_offer = None
        try:
            trade_offer_hex = file_path.read_text()
            trade_offer = TradeRecord.from_bytes(
                hexstr_to_bytes(trade_offer_hex))
        except Exception as e:
            return False, None, f"Error: {e}"
        if trade_offer is not None:
            offer_spend_bundle: SpendBundle = trade_offer.spend_bundle

        coinsols: List[CoinSolution] = []  # [] of CoinSolutions
        cc_coinsol_outamounts: Dict[bytes32, List[Tuple[CoinSolution,
                                                        int]]] = dict()
        aggsig = offer_spend_bundle.aggregated_signature
        cc_discrepancies: Dict[bytes32, int] = dict()
        chia_discrepancy = None
        wallets: Dict[bytes32, Any] = dict()  # colour to wallet dict

        for coinsol in offer_spend_bundle.coin_solutions:
            puzzle: Program = Program.from_bytes(bytes(coinsol.puzzle_reveal))
            solution: Program = Program.from_bytes(bytes(coinsol.solution))

            # work out the deficits between coin amount and expected output for each
            r = cc_utils.uncurry_cc(puzzle)
            if r:
                # Calculate output amounts
                mod_hash, genesis_checker, inner_puzzle = r
                colour = bytes(genesis_checker).hex()
                if colour not in wallets:
                    wallets[
                        colour] = await self.wallet_state_manager.get_wallet_for_colour(
                            colour)
                unspent = await self.wallet_state_manager.get_spendable_coins_for_wallet(
                    wallets[colour].id())
                if coinsol.coin in [record.coin for record in unspent]:
                    return False, None, "can't respond to own offer"

                innersol = solution.first()

                total = get_output_amount_for_puzzle_and_solution(
                    inner_puzzle, innersol)
                if colour in cc_discrepancies:
                    cc_discrepancies[colour] += coinsol.coin.amount - total
                else:
                    cc_discrepancies[colour] = coinsol.coin.amount - total
                # Store coinsol and output amount for later
                if colour in cc_coinsol_outamounts:
                    cc_coinsol_outamounts[colour].append((coinsol, total))
                else:
                    cc_coinsol_outamounts[colour] = [(coinsol, total)]

            else:
                # standard chia coin
                unspent = await self.wallet_state_manager.get_spendable_coins_for_wallet(
                    1)
                if coinsol.coin in [record.coin for record in unspent]:
                    return False, None, "can't respond to own offer"
                if chia_discrepancy is None:
                    chia_discrepancy = get_output_discrepancy_for_puzzle_and_solution(
                        coinsol.coin, puzzle, solution)
                else:
                    chia_discrepancy += get_output_discrepancy_for_puzzle_and_solution(
                        coinsol.coin, puzzle, solution)
                coinsols.append(coinsol)

        chia_spend_bundle: Optional[SpendBundle] = None
        if chia_discrepancy is not None:
            chia_spend_bundle = await self.wallet_state_manager.main_wallet.create_spend_bundle_relative_chia(
                chia_discrepancy, [])
            if chia_spend_bundle is not None:
                for coinsol in coinsols:
                    chia_spend_bundle.coin_solutions.append(coinsol)

        zero_spend_list: List[SpendBundle] = []
        spend_bundle = None
        # create coloured coin
        self.log.info(cc_discrepancies)
        for colour in cc_discrepancies.keys():
            if cc_discrepancies[colour] < 0:
                my_cc_spends = await wallets[colour].select_coins(
                    abs(cc_discrepancies[colour]))
            else:
                if chia_spend_bundle is None:
                    to_exclude: List = []
                else:
                    to_exclude = chia_spend_bundle.removals()
                my_cc_spends = await wallets[colour].select_coins(0)
                if my_cc_spends is None or my_cc_spends == set():
                    zero_spend_bundle: SpendBundle = await wallets[
                        colour].generate_zero_val_coin(False, to_exclude)
                    if zero_spend_bundle is None:
                        return (
                            False,
                            None,
                            "Unable to generate zero value coin. Confirm that you have chia available",
                        )
                    zero_spend_list.append(zero_spend_bundle)

                    additions = zero_spend_bundle.additions()
                    removals = zero_spend_bundle.removals()
                    my_cc_spends = set()
                    for add in additions:
                        if add not in removals and add.amount == 0:
                            my_cc_spends.add(add)

            if my_cc_spends == set() or my_cc_spends is None:
                return False, None, "insufficient funds"

            # Create SpendableCC list and innersol_list with both my coins and the offered coins
            # Firstly get the output coin
            my_output_coin = my_cc_spends.pop()
            spendable_cc_list = []
            innersol_list = []
            genesis_id = genesis_coin_id_for_genesis_coin_checker(
                Program.from_bytes(bytes.fromhex(colour)))
            # Make the rest of the coins assert the output coin is consumed
            for coloured_coin in my_cc_spends:
                inner_solution = self.wallet_state_manager.main_wallet.make_solution(
                    consumed=[my_output_coin.name()])
                inner_puzzle = await self.get_inner_puzzle_for_puzzle_hash(
                    coloured_coin.puzzle_hash)
                assert inner_puzzle is not None

                sigs = await wallets[colour].get_sigs(inner_puzzle,
                                                      inner_solution,
                                                      coloured_coin.name())
                sigs.append(aggsig)
                aggsig = AugSchemeMPL.aggregate(sigs)

                lineage_proof = await wallets[
                    colour].get_lineage_proof_for_coin(coloured_coin)
                spendable_cc_list.append(
                    SpendableCC(coloured_coin, genesis_id, inner_puzzle,
                                lineage_proof))
                innersol_list.append(inner_solution)

            # Create SpendableCC for each of the coloured coins received
            for cc_coinsol_out in cc_coinsol_outamounts[colour]:
                cc_coinsol = cc_coinsol_out[0]
                puzzle = Program.from_bytes(bytes(cc_coinsol.puzzle_reveal))
                solution = Program.from_bytes(bytes(cc_coinsol.solution))

                r = uncurry_cc(puzzle)
                if r:
                    mod_hash, genesis_coin_checker, inner_puzzle = r
                    inner_solution = solution.first()
                    lineage_proof = solution.rest().rest().first()
                    spendable_cc_list.append(
                        SpendableCC(cc_coinsol.coin, genesis_id, inner_puzzle,
                                    lineage_proof))
                    innersol_list.append(inner_solution)

            # Finish the output coin SpendableCC with new information
            newinnerpuzhash = await wallets[colour].get_new_inner_hash()
            outputamount = sum([
                c.amount for c in my_cc_spends
            ]) + cc_discrepancies[colour] + my_output_coin.amount
            inner_solution = self.wallet_state_manager.main_wallet.make_solution(
                primaries=[{
                    "puzzlehash": newinnerpuzhash,
                    "amount": outputamount
                }])
            inner_puzzle = await self.get_inner_puzzle_for_puzzle_hash(
                my_output_coin.puzzle_hash)
            assert inner_puzzle is not None

            lineage_proof = await wallets[colour].get_lineage_proof_for_coin(
                my_output_coin)
            spendable_cc_list.append(
                SpendableCC(my_output_coin, genesis_id, inner_puzzle,
                            lineage_proof))
            innersol_list.append(inner_solution)

            sigs = await wallets[colour].get_sigs(inner_puzzle, inner_solution,
                                                  my_output_coin.name())
            sigs.append(aggsig)
            aggsig = AugSchemeMPL.aggregate(sigs)
            if spend_bundle is None:
                spend_bundle = spend_bundle_for_spendable_ccs(
                    CC_MOD,
                    Program.from_bytes(bytes.fromhex(colour)),
                    spendable_cc_list,
                    innersol_list,
                    [aggsig],
                )
            else:
                new_spend_bundle = spend_bundle_for_spendable_ccs(
                    CC_MOD,
                    Program.from_bytes(bytes.fromhex(colour)),
                    spendable_cc_list,
                    innersol_list,
                    [aggsig],
                )
                spend_bundle = SpendBundle.aggregate(
                    [spend_bundle, new_spend_bundle])
            # reset sigs and aggsig so that they aren't included next time around
            sigs = []
            aggsig = AugSchemeMPL.aggregate(sigs)
        my_tx_records = []
        if zero_spend_list is not None and spend_bundle is not None:
            zero_spend_list.append(spend_bundle)
            spend_bundle = SpendBundle.aggregate(zero_spend_list)

        if spend_bundle is None:
            return False, None, "spend_bundle missing"

        # Add transaction history for this trade
        now = uint64(int(time.time()))
        if chia_spend_bundle is not None:
            spend_bundle = SpendBundle.aggregate(
                [spend_bundle, chia_spend_bundle])
            # debug_spend_bundle(spend_bundle)
            if chia_discrepancy < 0:
                tx_record = TransactionRecord(
                    confirmed_at_height=uint32(0),
                    created_at_time=now,
                    to_puzzle_hash=token_bytes(),
                    amount=uint64(abs(chia_discrepancy)),
                    fee_amount=uint64(0),
                    confirmed=False,
                    sent=uint32(10),
                    spend_bundle=chia_spend_bundle,
                    additions=chia_spend_bundle.additions(),
                    removals=chia_spend_bundle.removals(),
                    wallet_id=uint32(1),
                    sent_to=[],
                    trade_id=std_hash(spend_bundle.name() + bytes(now)),
                    type=uint32(TransactionType.OUTGOING_TRADE.value),
                    name=chia_spend_bundle.name(),
                )
            else:
                tx_record = TransactionRecord(
                    confirmed_at_height=uint32(0),
                    created_at_time=uint64(int(time.time())),
                    to_puzzle_hash=token_bytes(),
                    amount=uint64(abs(chia_discrepancy)),
                    fee_amount=uint64(0),
                    confirmed=False,
                    sent=uint32(10),
                    spend_bundle=chia_spend_bundle,
                    additions=chia_spend_bundle.additions(),
                    removals=chia_spend_bundle.removals(),
                    wallet_id=uint32(1),
                    sent_to=[],
                    trade_id=std_hash(spend_bundle.name() + bytes(now)),
                    type=uint32(TransactionType.INCOMING_TRADE.value),
                    name=chia_spend_bundle.name(),
                )
            my_tx_records.append(tx_record)

        for colour, amount in cc_discrepancies.items():
            wallet = wallets[colour]
            if chia_discrepancy > 0:
                tx_record = TransactionRecord(
                    confirmed_at_height=uint32(0),
                    created_at_time=uint64(int(time.time())),
                    to_puzzle_hash=token_bytes(),
                    amount=uint64(abs(amount)),
                    fee_amount=uint64(0),
                    confirmed=False,
                    sent=uint32(10),
                    spend_bundle=spend_bundle,
                    additions=spend_bundle.additions(),
                    removals=spend_bundle.removals(),
                    wallet_id=wallet.id(),
                    sent_to=[],
                    trade_id=std_hash(spend_bundle.name() + bytes(now)),
                    type=uint32(TransactionType.OUTGOING_TRADE.value),
                    name=spend_bundle.name(),
                )
            else:
                tx_record = TransactionRecord(
                    confirmed_at_height=uint32(0),
                    created_at_time=uint64(int(time.time())),
                    to_puzzle_hash=token_bytes(),
                    amount=uint64(abs(amount)),
                    fee_amount=uint64(0),
                    confirmed=False,
                    sent=uint32(10),
                    spend_bundle=spend_bundle,
                    additions=spend_bundle.additions(),
                    removals=spend_bundle.removals(),
                    wallet_id=wallet.id(),
                    sent_to=[],
                    trade_id=std_hash(spend_bundle.name() + bytes(now)),
                    type=uint32(TransactionType.INCOMING_TRADE.value),
                    name=token_bytes(),
                )
            my_tx_records.append(tx_record)

        tx_record = TransactionRecord(
            confirmed_at_height=uint32(0),
            created_at_time=uint64(int(time.time())),
            to_puzzle_hash=token_bytes(),
            amount=uint64(0),
            fee_amount=uint64(0),
            confirmed=False,
            sent=uint32(0),
            spend_bundle=spend_bundle,
            additions=spend_bundle.additions(),
            removals=spend_bundle.removals(),
            wallet_id=uint32(0),
            sent_to=[],
            trade_id=std_hash(spend_bundle.name() + bytes(now)),
            type=uint32(TransactionType.OUTGOING_TRADE.value),
            name=spend_bundle.name(),
        )

        now = uint64(int(time.time()))
        trade_record: TradeRecord = TradeRecord(
            confirmed_at_index=uint32(0),
            accepted_at_time=now,
            created_at_time=now,
            my_offer=False,
            sent=uint32(0),
            spend_bundle=offer_spend_bundle,
            tx_spend_bundle=spend_bundle,
            additions=spend_bundle.additions(),
            removals=spend_bundle.removals(),
            trade_id=std_hash(spend_bundle.name() + bytes(now)),
            status=uint32(TradeStatus.PENDING_CONFIRM.value),
            sent_to=[],
        )

        await self.save_trade(trade_record)
        await self.wallet_state_manager.add_pending_transaction(tx_record)
        for tx in my_tx_records:
            await self.wallet_state_manager.add_transaction(tx)

        return True, trade_record, None
    async def _create_offer_for_ids(
        self, offer: Dict[int, int]
    ) -> Tuple[bool, Optional[TradeRecord], Optional[str]]:
        """
        Offer is dictionary of wallet ids and amount
        """
        spend_bundle = None
        try:
            for id in offer.keys():
                amount = offer[id]
                wallet_id = uint32(int(id))
                wallet = self.wallet_state_manager.wallets[wallet_id]
                if isinstance(wallet, CCWallet):
                    balance = await wallet.get_confirmed_balance()
                    if balance < abs(amount) and amount < 0:
                        raise Exception(
                            f"insufficient funds in wallet {wallet_id}")
                    if amount > 0:
                        if spend_bundle is None:
                            to_exclude: List[Coin] = []
                        else:
                            to_exclude = spend_bundle.removals()
                        zero_spend_bundle: SpendBundle = await wallet.generate_zero_val_coin(
                            False, to_exclude)

                        if spend_bundle is None:
                            spend_bundle = zero_spend_bundle
                        else:
                            spend_bundle = SpendBundle.aggregate(
                                [spend_bundle, zero_spend_bundle])

                        additions = zero_spend_bundle.additions()
                        removals = zero_spend_bundle.removals()
                        zero_val_coin: Optional[Coin] = None
                        for add in additions:
                            if add not in removals and add.amount == 0:
                                zero_val_coin = add
                        new_spend_bundle = await wallet.create_spend_bundle_relative_amount(
                            amount, zero_val_coin)
                    else:
                        new_spend_bundle = await wallet.create_spend_bundle_relative_amount(
                            amount)
                elif isinstance(wallet, Wallet):
                    if spend_bundle is None:
                        to_exclude = []
                    else:
                        to_exclude = spend_bundle.removals()
                    new_spend_bundle = await wallet.create_spend_bundle_relative_chia(
                        amount, to_exclude)
                else:
                    return False, None, "unsupported wallet type"
                if new_spend_bundle is None or new_spend_bundle.removals(
                ) == []:
                    raise Exception(f"Wallet {id} was unable to create offer.")
                if spend_bundle is None:
                    spend_bundle = new_spend_bundle
                else:
                    spend_bundle = SpendBundle.aggregate(
                        [spend_bundle, new_spend_bundle])

            if spend_bundle is None:
                return False, None, None

            now = uint64(int(time.time()))
            trade_offer: TradeRecord = TradeRecord(
                confirmed_at_index=uint32(0),
                accepted_at_time=None,
                created_at_time=now,
                my_offer=True,
                sent=uint32(0),
                spend_bundle=spend_bundle,
                tx_spend_bundle=None,
                additions=spend_bundle.additions(),
                removals=spend_bundle.removals(),
                trade_id=std_hash(spend_bundle.name() + bytes(now)),
                status=uint32(TradeStatus.PENDING_ACCEPT.value),
                sent_to=[],
            )
            return True, trade_offer, None
        except Exception as e:
            tb = traceback.format_exc()
            self.log.error(f"Error with creating trade offer: {type(e)}{tb}")
            return False, None, str(e)
Beispiel #29
0
    async def _create_offer_for_ids(
        self, offer_dict: Dict[Union[int, bytes32], int], fee: uint64 = uint64(0)
    ) -> Tuple[bool, Optional[Offer], Optional[str]]:
        """
        Offer is dictionary of wallet ids and amount
        """
        try:
            coins_to_offer: Dict[uint32, List[Coin]] = {}
            requested_payments: Dict[Optional[bytes32], List[Payment]] = {}
            for id, amount in offer_dict.items():
                if amount > 0:
                    if isinstance(id, int):
                        wallet_id = uint32(id)
                        wallet = self.wallet_state_manager.wallets[wallet_id]
                        p2_ph: bytes32 = await wallet.get_new_puzzlehash()
                        if wallet.type() == WalletType.STANDARD_WALLET:
                            key: Optional[bytes32] = None
                            memos: List[bytes] = []
                        elif wallet.type() == WalletType.CAT:
                            key = bytes32(bytes.fromhex(wallet.get_asset_id()))
                            memos = [p2_ph]
                        else:
                            raise ValueError(f"Offers are not implemented for {wallet.type()}")
                    else:
                        p2_ph = await self.wallet_state_manager.main_wallet.get_new_puzzlehash()
                        key = id
                        memos = [p2_ph]
                    requested_payments[key] = [Payment(p2_ph, uint64(amount), memos)]
                elif amount < 0:
                    assert isinstance(id, int)
                    wallet_id = uint32(id)
                    wallet = self.wallet_state_manager.wallets[wallet_id]
                    balance = await wallet.get_confirmed_balance()
                    if balance < abs(amount):
                        raise Exception(f"insufficient funds in wallet {wallet_id}")
                    coins_to_offer[wallet_id] = await wallet.select_coins(uint64(abs(amount)))
                elif amount == 0:
                    raise ValueError("You cannot offer nor request 0 amount of something")

            all_coins: List[Coin] = [c for coins in coins_to_offer.values() for c in coins]
            notarized_payments: Dict[Optional[bytes32], List[NotarizedPayment]] = Offer.notarize_payments(
                requested_payments, all_coins
            )
            announcements_to_assert = Offer.calculate_announcements(notarized_payments)

            all_transactions: List[TransactionRecord] = []
            fee_left_to_pay: uint64 = fee
            for wallet_id, selected_coins in coins_to_offer.items():
                wallet = self.wallet_state_manager.wallets[wallet_id]
                # This should probably not switch on whether or not we're spending a CAT but it has to for now

                if wallet.type() == WalletType.CAT:
                    txs = await wallet.generate_signed_transaction(
                        [abs(offer_dict[int(wallet_id)])],
                        [Offer.ph()],
                        fee=fee_left_to_pay,
                        coins=set(selected_coins),
                        puzzle_announcements_to_consume=announcements_to_assert,
                    )
                    all_transactions.extend(txs)
                else:
                    tx = await wallet.generate_signed_transaction(
                        abs(offer_dict[int(wallet_id)]),
                        Offer.ph(),
                        fee=fee_left_to_pay,
                        coins=set(selected_coins),
                        puzzle_announcements_to_consume=announcements_to_assert,
                    )
                    all_transactions.append(tx)

                fee_left_to_pay = uint64(0)

            transaction_bundles: List[Optional[SpendBundle]] = [tx.spend_bundle for tx in all_transactions]
            total_spend_bundle = SpendBundle.aggregate(list(filter(lambda b: b is not None, transaction_bundles)))
            offer = Offer(notarized_payments, total_spend_bundle)
            return True, offer, None

        except Exception as e:
            tb = traceback.format_exc()
            self.log.error(f"Error with creating trade offer: {type(e)}{tb}")
            return False, None, str(e)
Beispiel #30
0
    def to_valid_spend(self,
                       arbitrage_ph: Optional[bytes32] = None) -> SpendBundle:
        if not self.is_valid():
            raise ValueError("Offer is currently incomplete")

        completion_spends: List[CoinSpend] = []
        for tail_hash, payments in self.requested_payments.items():
            offered_coins: List[Coin] = self.get_offered_coins()[tail_hash]

            # Because of CAT supply laws, we must specify a place for the leftovers to go
            arbitrage_amount: int = self.arbitrage()[tail_hash]
            all_payments: List[NotarizedPayment] = payments.copy()
            if arbitrage_amount > 0:
                assert arbitrage_amount is not None
                assert arbitrage_ph is not None
                all_payments.append(
                    NotarizedPayment(arbitrage_ph, uint64(arbitrage_amount),
                                     []))

            for coin in offered_coins:
                inner_solutions = []
                if coin == offered_coins[0]:
                    nonces: List[bytes32] = [p.nonce for p in all_payments]
                    for nonce in list(dict.fromkeys(
                            nonces)):  # dedup without messing with order
                        nonce_payments: List[NotarizedPayment] = list(
                            filter(lambda p: p.nonce == nonce, all_payments))
                        inner_solutions.append(
                            (nonce,
                             [np.as_condition_args()
                              for np in nonce_payments]))

                if tail_hash:
                    # CATs have a special way to be solved so we have to do some calculation before getting the solution
                    parent_spend: CoinSpend = list(
                        filter(
                            lambda cs: cs.coin.name() == coin.parent_coin_info,
                            self.bundle.coin_spends))[0]
                    parent_coin: Coin = parent_spend.coin
                    matched, curried_args = match_cat_puzzle(
                        parent_spend.puzzle_reveal.to_program())
                    assert matched
                    _, _, inner_puzzle = curried_args
                    spendable_cat = SpendableCAT(
                        coin,
                        tail_hash,
                        OFFER_MOD,
                        Program.to(inner_solutions),
                        lineage_proof=LineageProof(
                            parent_coin.parent_coin_info,
                            inner_puzzle.get_tree_hash(), parent_coin.amount),
                    )
                    solution: Program = (
                        unsigned_spend_bundle_for_spendable_cats(
                            CAT_MOD, [spendable_cat
                                      ]).coin_spends[0].solution.to_program())
                else:
                    solution = Program.to(inner_solutions)

                completion_spends.append(
                    CoinSpend(
                        coin,
                        construct_cat_puzzle(CAT_MOD, tail_hash, OFFER_MOD)
                        if tail_hash else OFFER_MOD,
                        solution,
                    ))

        return SpendBundle.aggregate(
            [SpendBundle(completion_spends, G2Element()), self.bundle])