async def generate_coins( self, sim, sim_client, requested_coins: Dict[Optional[str], List[uint64]], ) -> Dict[Optional[str], List[Coin]]: await sim.farm_block(acs_ph) parent_coin: Coin = [ cr.coin for cr in await ( sim_client.get_coin_records_by_puzzle_hash(acs_ph)) ][0] # We need to gather a list of initial coins to create as well as spends that do the eve spend for every CAT payments: List[Payment] = [] cat_bundles: List[SpendBundle] = [] for tail_str, amounts in requested_coins.items(): for amount in amounts: if tail_str: tail: Program = str_to_tail( tail_str) # Making a fake but unique TAIL cat_puzzle: Program = construct_cat_puzzle( CAT_MOD, tail.get_tree_hash(), acs) payments.append( Payment(cat_puzzle.get_tree_hash(), amount, [])) cat_bundles.append( unsigned_spend_bundle_for_spendable_cats( CAT_MOD, [ SpendableCAT( Coin(parent_coin.name(), cat_puzzle.get_tree_hash(), amount), tail.get_tree_hash(), acs, Program.to([[51, acs_ph, amount], [51, 0, -113, tail, []]]), ) ], )) else: payments.append(Payment(acs_ph, amount, [])) # This bundle create all of the initial coins parent_bundle = SpendBundle( [ CoinSpend( parent_coin, acs, Program.to([[51, p.puzzle_hash, p.amount] for p in payments]), ) ], G2Element(), ) # Then we aggregate it with all of the eve spends await sim_client.push_tx( SpendBundle.aggregate([parent_bundle, *cat_bundles])) await sim.farm_block() # Search for all of the coins and put them into a dictionary coin_dict: Dict[Optional[str], List[Coin]] = {} for tail_str, _ in requested_coins.items(): if tail_str: tail_hash: bytes32 = str_to_tail_hash(tail_str) cat_ph: bytes32 = construct_cat_puzzle(CAT_MOD, tail_hash, acs).get_tree_hash() coin_dict[tail_str] = [ cr.coin for cr in await ( sim_client.get_coin_records_by_puzzle_hash( cat_ph, include_spent_coins=False)) ] else: coin_dict[None] = list( filter( lambda c: c.amount < 250000000000, [ cr.coin for cr in await ( sim_client.get_coin_records_by_puzzle_hash( acs_ph, include_spent_coins=False)) ], )) return coin_dict
from src.types.condition_opcodes import ConditionOpcode from src.types.program import Program from src.types.sized_bytes import bytes32 from src.types.spend_bundle import CoinSolution, SpendBundle from src.util.condition_tools import conditions_dict_for_solution from src.util.ints import uint64 from src.wallet.puzzles.cc_loader import CC_MOD, LOCK_INNER_PUZZLE from src.wallet.puzzles.genesis_by_coin_id_with_0 import ( lineage_proof_for_genesis, lineage_proof_for_coin, lineage_proof_for_zero, genesis_coin_id_for_genesis_coin_checker, ) NULL_SIGNATURE = G2Element.generator() * 0 ANYONE_CAN_SPEND_PUZZLE = Program.to(1) # simply return the conditions # information needed to spend a cc # if we ever support more genesis conditions, like a re-issuable coin, # we may need also to save the `genesis_coin_mod` or its hash @dataclasses.dataclass class SpendableCC: coin: Coin genesis_coin_id: bytes32 inner_puzzle: Program lineage_proof: Program
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, )
def test_singleton_top_layer(self): # START TESTS # Generate starting info key_lookup = KeyTool() pk: G1Element = public_key_for_index(1, key_lookup) starting_puzzle: Program = p2_delegated_puzzle_or_hidden_puzzle.puzzle_for_pk( pk) # noqa adapted_puzzle: Program = singleton_top_layer.adapt_inner_to_singleton( starting_puzzle) # noqa adapted_puzzle_hash: bytes32 = adapted_puzzle.get_tree_hash() # Get our starting standard coin created START_AMOUNT: uint64 = 1023 coin_db = CoinStore() coin_db.farm_coin(starting_puzzle.get_tree_hash(), T1, START_AMOUNT) starting_coin: Coin = next(coin_db.all_unspent_coins()) comment: List[Tuple[str, str]] = [("hello", "world")] # LAUNCHING # Try to create an even singleton (driver test) try: conditions, launcher_coinsol = singleton_top_layer.launch_conditions_and_coinsol( # noqa starting_coin, adapted_puzzle, comment, (START_AMOUNT - 1)) raise AssertionError("This should fail due to an even amount") except ValueError as msg: assert str(msg) == "Coin amount cannot be even. Subtract one mojo." conditions, launcher_coinsol = singleton_top_layer.launch_conditions_and_coinsol( # noqa starting_coin, adapted_puzzle, comment, START_AMOUNT) # Creating solution for standard transaction delegated_puzzle: Program = p2_conditions.puzzle_for_conditions( conditions) # noqa full_solution: Program = p2_delegated_puzzle_or_hidden_puzzle.solution_for_conditions( conditions) # noqa starting_coinsol = CoinSolution( starting_coin, starting_puzzle, full_solution, ) make_and_spend_bundle( coin_db, starting_coin, delegated_puzzle, [starting_coinsol, launcher_coinsol], ) # EVE singleton_eve: Coin = next(coin_db.all_unspent_coins()) launcher_coin: Coin = singleton_top_layer.generate_launcher_coin( starting_coin, START_AMOUNT, ) launcher_id: bytes32 = launcher_coin.name() # This delegated puzzle just recreates the coin exactly delegated_puzzle: Program = Program.to(( 1, [[ ConditionOpcode.CREATE_COIN, adapted_puzzle_hash, singleton_eve.amount, ]], )) inner_solution: Program = Program.to([[], delegated_puzzle, []]) # Generate the lineage proof we will need from the launcher coin lineage_proof: LineageProof = singleton_top_layer.lineage_proof_for_coinsol( launcher_coinsol) # noqa puzzle_reveal: Program = singleton_top_layer.puzzle_for_singleton( launcher_id, adapted_puzzle, ) full_solution: Program = singleton_top_layer.solution_for_singleton( lineage_proof, singleton_eve.amount, inner_solution, ) singleton_eve_coinsol = CoinSolution( singleton_eve, puzzle_reveal, full_solution, ) make_and_spend_bundle( coin_db, singleton_eve, delegated_puzzle, [singleton_eve_coinsol], ) # POST-EVE singleton: Coin = next(coin_db.all_unspent_coins()) # Same delegated_puzzle / inner_solution. We're just recreating ourself lineage_proof: LineageProof = singleton_top_layer.lineage_proof_for_coinsol( singleton_eve_coinsol) # noqa # Same puzzle_reveal too full_solution: Program = singleton_top_layer.solution_for_singleton( lineage_proof, singleton.amount, inner_solution, ) singleton_coinsol = CoinSolution( singleton, puzzle_reveal, full_solution, ) make_and_spend_bundle( coin_db, singleton, delegated_puzzle, [singleton_coinsol], ) # CLAIM A P2_SINGLETON singleton_child: Coin = next(coin_db.all_unspent_coins()) p2_singleton_puz: Program = singleton_top_layer.pay_to_singleton_puzzle( launcher_id) p2_singleton_ph: bytes32 = p2_singleton_puz.get_tree_hash() ARBITRARY_AMOUNT: uint64 = 1379 coin_db.farm_coin(p2_singleton_ph, T1, ARBITRARY_AMOUNT) p2_singleton_coin: Coin = list( filter( lambda e: e.amount == ARBITRARY_AMOUNT, list(coin_db.all_unspent_coins()), ))[0] assertion, announcement, claim_coinsol = singleton_top_layer.claim_p2_singleton( p2_singleton_coin, adapted_puzzle_hash, launcher_id, ) delegated_puzzle: Program = Program.to(( 1, [ [ ConditionOpcode.CREATE_COIN, adapted_puzzle_hash, singleton_eve.amount ], assertion, announcement, ], )) inner_solution: Program = Program.to([[], delegated_puzzle, []]) lineage_proof: LineageProof = singleton_top_layer.lineage_proof_for_coinsol( singleton_coinsol) puzzle_reveal: Program = singleton_top_layer.puzzle_for_singleton( launcher_id, adapted_puzzle, ) full_solution: Program = singleton_top_layer.solution_for_singleton( lineage_proof, singleton_eve.amount, inner_solution, ) singleton_claim_coinsol = CoinSolution( singleton_child, puzzle_reveal, full_solution, ) make_and_spend_bundle(coin_db, singleton_child, delegated_puzzle, [singleton_claim_coinsol, claim_coinsol]) # CLAIM A P2_SINGLETON_OR_DELAYED singleton_child: Coin = next(coin_db.all_unspent_coins()) DELAY_TIME: uint64 = 1 DELAY_PH: bytes32 = adapted_puzzle_hash p2_singleton_puz: Program = singleton_top_layer.pay_to_singleton_or_delay_puzzle( launcher_id, DELAY_TIME, DELAY_PH, ) p2_singleton_ph: bytes32 = p2_singleton_puz.get_tree_hash() ARBITRARY_AMOUNT: uint64 = 1379 coin_db.farm_coin(p2_singleton_ph, T1, ARBITRARY_AMOUNT) p2_singleton_coin: Coin = list( filter( lambda e: e.amount == ARBITRARY_AMOUNT, list(coin_db.all_unspent_coins()), ))[0] assertion, announcement, claim_coinsol = singleton_top_layer.claim_p2_singleton( p2_singleton_coin, adapted_puzzle_hash, launcher_id, ) delegated_puzzle: Program = Program.to(( 1, [ [ ConditionOpcode.CREATE_COIN, adapted_puzzle_hash, singleton_eve.amount ], assertion, announcement, ], )) inner_solution: Program = Program.to([[], delegated_puzzle, []]) lineage_proof: LineageProof = singleton_top_layer.lineage_proof_for_coinsol( singleton_coinsol) puzzle_reveal: Program = singleton_top_layer.puzzle_for_singleton( launcher_id, adapted_puzzle, ) full_solution: Program = singleton_top_layer.solution_for_singleton( lineage_proof, singleton_eve.amount, inner_solution, ) delay_claim_coinsol = CoinSolution( singleton_child, puzzle_reveal, full_solution, ) # Fork it so we can try the other spend types fork_coin_db: CoinStore = copy.deepcopy(coin_db) fork_coin_db_2: CoinStore = copy.deepcopy(coin_db) make_and_spend_bundle(coin_db, singleton_child, delegated_puzzle, [delay_claim_coinsol, claim_coinsol]) # TRY TO SPEND AWAY TOO SOON (Negative Test) to_delay_ph_coinsol = singleton_top_layer.spend_to_delayed_puzzle( p2_singleton_coin, ARBITRARY_AMOUNT, launcher_id, DELAY_TIME, DELAY_PH, ) try: fork_coin_db.update_coin_store_for_spend_bundle( SpendBundle([to_delay_ph_coinsol], G2Element()), T1, DEFAULT_CONSTANTS.MAX_BLOCK_COST_CLVM, ) except BadSpendBundleError as e: assert str( e ) == "condition validation failure Err.ASSERT_SECONDS_RELATIVE_FAILED" # SPEND TO DELAYED PUZZLE HASH fork_coin_db_2.update_coin_store_for_spend_bundle( SpendBundle([to_delay_ph_coinsol], G2Element()), CoinTimestamp(100, 10000005), DEFAULT_CONSTANTS.MAX_BLOCK_COST_CLVM, ) # CREATE MULTIPLE ODD CHILDREN (Negative Test) singleton_child: Coin = next(coin_db.all_unspent_coins()) delegated_puzzle: Program = Program.to(( 1, [ [ConditionOpcode.CREATE_COIN, adapted_puzzle_hash, 3], [ConditionOpcode.CREATE_COIN, adapted_puzzle_hash, 7], ], )) inner_solution: Program = Program.to([[], delegated_puzzle, []]) lineage_proof: LineageProof = singleton_top_layer.lineage_proof_for_coinsol( singleton_coinsol) # noqa puzzle_reveal: Program = singleton_top_layer.puzzle_for_singleton( launcher_id, adapted_puzzle, ) full_solution: Program = singleton_top_layer.solution_for_singleton( lineage_proof, singleton_child.amount, inner_solution) multi_odd_coinsol = CoinSolution( singleton_child, puzzle_reveal, full_solution, ) make_and_spend_bundle( coin_db, singleton_child, delegated_puzzle, [multi_odd_coinsol], exception=BadSpendBundleError, ex_msg="clvm validation failure Err.SEXP_ERROR", fail_msg="Too many odd children were allowed", ) # CREATE NO ODD CHILDREN (Negative Test) delegated_puzzle: Program = Program.to(( 1, [ [ConditionOpcode.CREATE_COIN, adapted_puzzle_hash, 4], [ConditionOpcode.CREATE_COIN, adapted_puzzle_hash, 10], ], )) inner_solution: Program = Program.to([[], delegated_puzzle, []]) lineage_proof: LineageProof = singleton_top_layer.lineage_proof_for_coinsol( singleton_coinsol) # noqa puzzle_reveal: Program = singleton_top_layer.puzzle_for_singleton( launcher_id, adapted_puzzle, ) full_solution: Program = singleton_top_layer.solution_for_singleton( lineage_proof, singleton_child.amount, inner_solution) no_odd_coinsol = CoinSolution( singleton_child, puzzle_reveal, full_solution, ) make_and_spend_bundle( coin_db, singleton_child, delegated_puzzle, [no_odd_coinsol], exception=BadSpendBundleError, ex_msg="clvm validation failure Err.SEXP_ERROR", fail_msg="Need at least one odd child", ) # ATTEMPT TO CREATE AN EVEN SINGLETON (Negative test) fork_coin_db: CoinStore = copy.deepcopy(coin_db) delegated_puzzle: Program = Program.to(( 1, [ [ ConditionOpcode.CREATE_COIN, singleton_child.puzzle_hash, 2, ], [ConditionOpcode.CREATE_COIN, adapted_puzzle_hash, 1], ], )) inner_solution: Program = Program.to([[], delegated_puzzle, []]) lineage_proof: LineageProof = singleton_top_layer.lineage_proof_for_coinsol( delay_claim_coinsol) puzzle_reveal: Program = singleton_top_layer.puzzle_for_singleton( launcher_id, adapted_puzzle, ) full_solution: Program = singleton_top_layer.solution_for_singleton( lineage_proof, singleton_child.amount, inner_solution) singleton_even_coinsol = CoinSolution( singleton_child, puzzle_reveal, full_solution, ) make_and_spend_bundle( fork_coin_db, singleton_child, delegated_puzzle, [singleton_even_coinsol], ) # Now try a perfectly innocent spend evil_coin: Coin = next(fork_coin_db.all_unspent_coins()) delegated_puzzle: Program = Program.to(( 1, [ [ ConditionOpcode.CREATE_COIN, adapted_puzzle_hash, 1, ], ], )) inner_solution: Program = Program.to([[], delegated_puzzle, []]) lineage_proof: LineageProof = singleton_top_layer.lineage_proof_for_coinsol( singleton_even_coinsol) # noqa puzzle_reveal: Program = singleton_top_layer.puzzle_for_singleton( launcher_id, adapted_puzzle, ) full_solution: Program = singleton_top_layer.solution_for_singleton( lineage_proof, 1, inner_solution, ) evil_coinsol = CoinSolution( evil_coin, puzzle_reveal, full_solution, ) make_and_spend_bundle( fork_coin_db, evil_coin, delegated_puzzle, [evil_coinsol], exception=BadSpendBundleError, ex_msg="condition validation failure Err.ASSERT_MY_COIN_ID_FAILED", fail_msg="This coin is even!", ) # MELTING # Remember, we're still spending singleton_child conditions = [ singleton_top_layer.MELT_CONDITION, [ ConditionOpcode.CREATE_COIN, adapted_puzzle_hash, (singleton_child.amount - 1), ], ] delegated_puzzle: Program = p2_conditions.puzzle_for_conditions( conditions) inner_solution: Program = p2_delegated_puzzle_or_hidden_puzzle.solution_for_conditions( conditions) lineage_proof: LineageProof = singleton_top_layer.lineage_proof_for_coinsol( delay_claim_coinsol) puzzle_reveal: Program = singleton_top_layer.puzzle_for_singleton( launcher_id, adapted_puzzle, ) full_solution: Program = singleton_top_layer.solution_for_singleton( lineage_proof, singleton_child.amount, inner_solution) melt_coinsol = CoinSolution( singleton_child, puzzle_reveal, full_solution, ) make_and_spend_bundle( coin_db, singleton_child, delegated_puzzle, [melt_coinsol], ) melted_coin = next(coin_db.all_unspent_coins()) assert melted_coin.puzzle_hash == adapted_puzzle_hash
def test_readme(): seed: bytes = bytes([ 0, 50, 6, 244, 24, 199, 1, 25, 52, 88, 192, 19, 18, 12, 89, 6, 220, 18, 102, 58, 209, 82, 12, 62, 89, 110, 182, 9, 44, 20, 254, 22, ]) sk: PrivateKey = AugSchemeMPL.key_gen(seed) pk: G1Element = sk.get_g1() message: bytes = bytes([1, 2, 3, 4, 5]) signature: G2Element = AugSchemeMPL.sign(sk, message) ok: bool = AugSchemeMPL.verify(pk, message, signature) assert ok sk_bytes: bytes = bytes(sk) # 32 bytes pk_bytes: bytes = bytes(pk) # 48 bytes signature_bytes: bytes = bytes(signature) # 96 bytes print(sk_bytes.hex(), pk_bytes.hex(), signature_bytes.hex()) sk = PrivateKey.from_bytes(sk_bytes) pk = G1Element.from_bytes(pk_bytes) signature = G2Element.from_bytes(signature_bytes) seed = bytes([1]) + seed[1:] sk1: PrivateKey = AugSchemeMPL.key_gen(seed) seed = bytes([2]) + seed[1:] sk2: PrivateKey = AugSchemeMPL.key_gen(seed) message2: bytes = bytes([1, 2, 3, 4, 5, 6, 7]) pk1: G1Element = sk1.get_g1() sig1: G2Element = AugSchemeMPL.sign(sk1, message) pk2: G1Element = sk2.get_g1() sig2: G2Element = AugSchemeMPL.sign(sk2, message2) agg_sig: G2Element = AugSchemeMPL.aggregate([sig1, sig2]) ok = AugSchemeMPL.aggregate_verify([pk1, pk2], [message, message2], agg_sig) assert ok seed = bytes([3]) + seed[1:] sk3: PrivateKey = AugSchemeMPL.key_gen(seed) pk3: G1Element = sk3.get_g1() message3: bytes = bytes([100, 2, 254, 88, 90, 45, 23]) sig3: G2Element = AugSchemeMPL.sign(sk3, message3) agg_sig_final: G2Element = AugSchemeMPL.aggregate([agg_sig, sig3]) ok = AugSchemeMPL.aggregate_verify([pk1, pk2, pk3], [message, message2, message3], agg_sig_final) assert ok pop_sig1: G2Element = PopSchemeMPL.sign(sk1, message) pop_sig2: G2Element = PopSchemeMPL.sign(sk2, message) pop_sig3: G2Element = PopSchemeMPL.sign(sk3, message) pop1: G2Element = PopSchemeMPL.pop_prove(sk1) pop2: G2Element = PopSchemeMPL.pop_prove(sk2) pop3: G2Element = PopSchemeMPL.pop_prove(sk3) ok = PopSchemeMPL.pop_verify(pk1, pop1) assert ok ok = PopSchemeMPL.pop_verify(pk2, pop2) assert ok ok = PopSchemeMPL.pop_verify(pk3, pop3) assert ok pop_sig_agg: G2Element = PopSchemeMPL.aggregate( [pop_sig1, pop_sig2, pop_sig3]) ok = PopSchemeMPL.fast_aggregate_verify([pk1, pk2, pk3], message, pop_sig_agg) assert ok pop_agg_pk: G1Element = pk1 + pk2 + pk3 ok = PopSchemeMPL.verify(pop_agg_pk, message, pop_sig_agg) assert ok pop_agg_sk: PrivateKey = PrivateKey.aggregate([sk1, sk2, sk3]) ok = PopSchemeMPL.sign(pop_agg_sk, message) == pop_sig_agg assert ok master_sk: PrivateKey = AugSchemeMPL.key_gen(seed) child: PrivateKey = AugSchemeMPL.derive_child_sk(master_sk, 152) grandchild: PrivateKey = AugSchemeMPL.derive_child_sk(child, 952) master_pk: G1Element = master_sk.get_g1() child_u: PrivateKey = AugSchemeMPL.derive_child_sk_unhardened( master_sk, 22) grandchild_u: PrivateKey = AugSchemeMPL.derive_child_sk_unhardened( child_u, 0) child_u_pk: G1Element = AugSchemeMPL.derive_child_pk_unhardened( master_pk, 22) grandchild_u_pk: G1Element = AugSchemeMPL.derive_child_pk_unhardened( child_u_pk, 0) ok = grandchild_u_pk == grandchild_u.get_g1() assert ok
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()
from chia.types.spend_bundle import CoinSpend, SpendBundle from chia.util.ints import uint64 from chia.wallet.cc_wallet.cc_utils import ( CC_MOD, cc_puzzle_for_inner_puzzle, cc_puzzle_hash_for_inner_puzzle_hash, spend_bundle_for_spendable_ccs, spendable_cc_list_from_coin_spend, ) from chia.wallet.puzzles.genesis_by_coin_id_with_0 import create_genesis_or_zero_coin_checker from chia.wallet.puzzles.genesis_by_puzzle_hash_with_0 import create_genesis_puzzle_or_zero_coin_checker CONDITIONS = dict( (k, bytes(v)[0]) for k, v in ConditionOpcode.__members__.items()) # pylint: disable=E1101 NULL_SIGNATURE = G2Element() ANYONE_CAN_SPEND_PUZZLE = Program.to(1) # simply return the conditions PUZZLE_TABLE: Dict[bytes32, Program] = dict( (_.get_tree_hash(), _) for _ in [ANYONE_CAN_SPEND_PUZZLE]) def hash_to_puzzle_f(puzzle_hash: bytes32) -> Optional[Program]: return PUZZLE_TABLE.get(puzzle_hash) def add_puzzles_to_puzzle_preimage_db(puzzles: List[Program]) -> None: for _ in puzzles: PUZZLE_TABLE[_.get_tree_hash()] = _
def test_pool_lifecycle(self): # START TESTS # Generate starting info key_lookup = KeyTool() sk: PrivateKey = PrivateKey.from_bytes( secret_exponent_for_index(1).to_bytes(32, "big"), ) pk: G1Element = G1Element.from_bytes( public_key_for_index(1, key_lookup)) starting_puzzle: Program = puzzle_for_pk(pk) starting_ph: bytes32 = starting_puzzle.get_tree_hash() # Get our starting standard coin created START_AMOUNT: uint64 = 1023 coin_db = CoinStore() time = CoinTimestamp(10000000, 1) coin_db.farm_coin(starting_ph, time, START_AMOUNT) starting_coin: Coin = next(coin_db.all_unspent_coins()) # LAUNCHING # Create the escaping inner puzzle GENESIS_CHALLENGE = bytes32.fromhex( "ccd5bb71183532bff220ba46c268991a3ff07eb358e8255a65c30a2dce0e5fbb") launcher_coin = singleton_top_layer.generate_launcher_coin( starting_coin, START_AMOUNT, ) DELAY_TIME = uint64(60800) DELAY_PH = starting_ph launcher_id = launcher_coin.name() relative_lock_height: uint32 = uint32(5000) # use a dummy pool state pool_state = PoolState( owner_pubkey=pk, pool_url="", relative_lock_height=relative_lock_height, state=3, # farming to pool target_puzzle_hash=starting_ph, version=1, ) # create a new dummy pool state for travelling target_pool_state = PoolState( owner_pubkey=pk, pool_url="", relative_lock_height=relative_lock_height, state=2, # Leaving pool target_puzzle_hash=starting_ph, version=1, ) # Standard format comment comment = Program.to([("p", bytes(pool_state)), ("t", DELAY_TIME), ("h", DELAY_PH)]) pool_wr_innerpuz: bytes32 = create_waiting_room_inner_puzzle( starting_ph, relative_lock_height, pk, launcher_id, GENESIS_CHALLENGE, DELAY_TIME, DELAY_PH, ) pool_wr_inner_hash = pool_wr_innerpuz.get_tree_hash() pooling_innerpuz: Program = create_pooling_inner_puzzle( starting_ph, pool_wr_inner_hash, pk, launcher_id, GENESIS_CHALLENGE, DELAY_TIME, DELAY_PH, ) # Driver tests assert is_pool_singleton_inner_puzzle(pooling_innerpuz) assert is_pool_singleton_inner_puzzle(pool_wr_innerpuz) assert get_pubkey_from_member_inner_puzzle(pooling_innerpuz) == pk # Generating launcher information conditions, launcher_coinsol = singleton_top_layer.launch_conditions_and_coinsol( starting_coin, pooling_innerpuz, comment, START_AMOUNT) # Creating solution for standard transaction delegated_puzzle: Program = puzzle_for_conditions(conditions) full_solution: Program = solution_for_conditions(conditions) starting_coinsol = CoinSpend( starting_coin, starting_puzzle, full_solution, ) # Create the spend bundle sig: G2Element = sign_delegated_puz(delegated_puzzle, starting_coin) spend_bundle = SpendBundle( [starting_coinsol, launcher_coinsol], sig, ) # Spend it! coin_db.update_coin_store_for_spend_bundle( spend_bundle, time, DEFAULT_CONSTANTS.MAX_BLOCK_COST_CLVM, ) # Test that we can retrieve the extra data assert get_delayed_puz_info_from_launcher_spend(launcher_coinsol) == ( DELAY_TIME, DELAY_PH) assert solution_to_pool_state(launcher_coinsol) == pool_state # TEST TRAVEL AFTER LAUNCH # fork the state fork_coin_db: CoinStore = copy.deepcopy(coin_db) post_launch_coinsol, _ = create_travel_spend( launcher_coinsol, launcher_coin, pool_state, target_pool_state, GENESIS_CHALLENGE, DELAY_TIME, DELAY_PH, ) # Spend it! fork_coin_db.update_coin_store_for_spend_bundle( SpendBundle([post_launch_coinsol], G2Element()), time, DEFAULT_CONSTANTS.MAX_BLOCK_COST_CLVM, ) # HONEST ABSORB time = CoinTimestamp(10000030, 2) # create the farming reward p2_singleton_puz: Program = create_p2_singleton_puzzle( SINGLETON_MOD_HASH, launcher_id, DELAY_TIME, DELAY_PH, ) p2_singleton_ph: bytes32 = p2_singleton_puz.get_tree_hash() assert uncurry_pool_waitingroom_inner_puzzle(pool_wr_innerpuz) == ( starting_ph, relative_lock_height, pk, p2_singleton_ph, ) assert launcher_id_to_p2_puzzle_hash(launcher_id, DELAY_TIME, DELAY_PH) == p2_singleton_ph assert get_seconds_and_delayed_puzhash_from_p2_singleton_puzzle( p2_singleton_puz) == (DELAY_TIME, DELAY_PH) coin_db.farm_coin(p2_singleton_ph, time, 1750000000000) coin_sols: List[CoinSpend] = create_absorb_spend( launcher_coinsol, pool_state, launcher_coin, 2, GENESIS_CHALLENGE, DELAY_TIME, DELAY_PH, # height ) # Spend it! coin_db.update_coin_store_for_spend_bundle( SpendBundle(coin_sols, G2Element()), time, DEFAULT_CONSTANTS.MAX_BLOCK_COST_CLVM, ) # ABSORB A NON EXISTENT REWARD (Negative test) last_coinsol: CoinSpend = list( filter( lambda e: e.coin.amount == START_AMOUNT, coin_sols, ))[0] coin_sols: List[CoinSpend] = create_absorb_spend( last_coinsol, pool_state, launcher_coin, 2, GENESIS_CHALLENGE, DELAY_TIME, DELAY_PH, # height ) # filter for only the singleton solution singleton_coinsol: CoinSpend = list( filter( lambda e: e.coin.amount == START_AMOUNT, coin_sols, ))[0] # Spend it and hope it fails! try: coin_db.update_coin_store_for_spend_bundle( SpendBundle([singleton_coinsol], G2Element()), time, DEFAULT_CONSTANTS.MAX_BLOCK_COST_CLVM, ) except BadSpendBundleError as e: assert str( e ) == "condition validation failure Err.ASSERT_ANNOUNCE_CONSUMED_FAILED" # SPEND A NON-REWARD P2_SINGLETON (Negative test) # create the dummy coin non_reward_p2_singleton = Coin( bytes32(32 * b"3"), p2_singleton_ph, uint64(1337), ) coin_db._add_coin_entry(non_reward_p2_singleton, time) # construct coin solution for the p2_singleton coin bad_coinsol = CoinSpend( non_reward_p2_singleton, p2_singleton_puz, Program.to([ pooling_innerpuz.get_tree_hash(), non_reward_p2_singleton.name(), ]), ) # Spend it and hope it fails! try: coin_db.update_coin_store_for_spend_bundle( SpendBundle([singleton_coinsol, bad_coinsol], G2Element()), time, DEFAULT_CONSTANTS.MAX_BLOCK_COST_CLVM, ) except BadSpendBundleError as e: assert str( e ) == "condition validation failure Err.ASSERT_ANNOUNCE_CONSUMED_FAILED" # ENTER WAITING ROOM # find the singleton singleton = get_most_recent_singleton_coin_from_coin_spend( last_coinsol) # get the relevant coin solution travel_coinsol, _ = create_travel_spend( last_coinsol, launcher_coin, pool_state, target_pool_state, GENESIS_CHALLENGE, DELAY_TIME, DELAY_PH, ) # Test that we can retrieve the extra data assert solution_to_pool_state(travel_coinsol) == target_pool_state # sign the serialized state data = Program.to(bytes(target_pool_state)).get_tree_hash() sig: G2Element = AugSchemeMPL.sign( sk, (data + singleton.name() + DEFAULT_CONSTANTS.AGG_SIG_ME_ADDITIONAL_DATA), ) # Spend it! coin_db.update_coin_store_for_spend_bundle( SpendBundle([travel_coinsol], sig), time, DEFAULT_CONSTANTS.MAX_BLOCK_COST_CLVM, ) # ESCAPE TOO FAST (Negative test) # find the singleton singleton = get_most_recent_singleton_coin_from_coin_spend( travel_coinsol) # get the relevant coin solution return_coinsol, _ = create_travel_spend( travel_coinsol, launcher_coin, target_pool_state, pool_state, GENESIS_CHALLENGE, DELAY_TIME, DELAY_PH, ) # sign the serialized target state sig = AugSchemeMPL.sign( sk, (data + singleton.name() + DEFAULT_CONSTANTS.AGG_SIG_ME_ADDITIONAL_DATA), ) # Spend it and hope it fails! try: coin_db.update_coin_store_for_spend_bundle( SpendBundle([return_coinsol], sig), time, DEFAULT_CONSTANTS.MAX_BLOCK_COST_CLVM, ) except BadSpendBundleError as e: assert str( e ) == "condition validation failure Err.ASSERT_HEIGHT_RELATIVE_FAILED" # ABSORB WHILE IN WAITING ROOM time = CoinTimestamp(10000060, 3) # create the farming reward coin_db.farm_coin(p2_singleton_ph, time, 1750000000000) # generate relevant coin solutions coin_sols: List[CoinSpend] = create_absorb_spend( travel_coinsol, target_pool_state, launcher_coin, 3, GENESIS_CHALLENGE, DELAY_TIME, DELAY_PH, # height ) # Spend it! coin_db.update_coin_store_for_spend_bundle( SpendBundle(coin_sols, G2Element()), time, DEFAULT_CONSTANTS.MAX_BLOCK_COST_CLVM, ) # LEAVE THE WAITING ROOM time = CoinTimestamp(20000000, 10000) # find the singleton singleton_coinsol: CoinSpend = list( filter( lambda e: e.coin.amount == START_AMOUNT, coin_sols, ))[0] singleton: Coin = get_most_recent_singleton_coin_from_coin_spend( singleton_coinsol) # get the relevant coin solution return_coinsol, _ = create_travel_spend( singleton_coinsol, launcher_coin, target_pool_state, pool_state, GENESIS_CHALLENGE, DELAY_TIME, DELAY_PH, ) # Test that we can retrieve the extra data assert solution_to_pool_state(return_coinsol) == pool_state # sign the serialized target state data = Program.to([ pooling_innerpuz.get_tree_hash(), START_AMOUNT, bytes(pool_state) ]).get_tree_hash() sig: G2Element = AugSchemeMPL.sign( sk, (data + singleton.name() + DEFAULT_CONSTANTS.AGG_SIG_ME_ADDITIONAL_DATA), ) # Spend it! coin_db.update_coin_store_for_spend_bundle( SpendBundle([return_coinsol], sig), time, DEFAULT_CONSTANTS.MAX_BLOCK_COST_CLVM, ) # ABSORB ONCE MORE FOR GOOD MEASURE time = CoinTimestamp(20000000, 10005) # create the farming reward coin_db.farm_coin(p2_singleton_ph, time, 1750000000000) coin_sols: List[CoinSpend] = create_absorb_spend( return_coinsol, pool_state, launcher_coin, 10005, GENESIS_CHALLENGE, DELAY_TIME, DELAY_PH, # height ) # Spend it! coin_db.update_coin_store_for_spend_bundle( SpendBundle(coin_sols, G2Element()), time, DEFAULT_CONSTANTS.MAX_BLOCK_COST_CLVM, )
def create_unfinished_block( constants: ConsensusConstants, sub_slot_start_total_iters: uint128, sub_slot_iters: uint64, signage_point_index: uint8, sp_iters: uint64, ip_iters: uint64, proof_of_space: ProofOfSpace, slot_cc_challenge: bytes32, farmer_reward_puzzle_hash: bytes32, pool_target: PoolTarget, get_plot_signature: Callable[[bytes32, G1Element], G2Element], get_pool_signature: Callable[[PoolTarget, Optional[G1Element]], Optional[G2Element]], signage_point: SignagePoint, timestamp: uint64, blocks: BlockchainInterface, seed: bytes32 = b"", # type: ignore[assignment] block_generator: Optional[BlockGenerator] = None, aggregate_sig: G2Element = G2Element(), additions: Optional[List[Coin]] = None, removals: Optional[List[Coin]] = None, prev_block: Optional[BlockRecord] = None, finished_sub_slots_input: List[EndOfSubSlotBundle] = None, ) -> UnfinishedBlock: """ Creates a new unfinished block using all the information available at the signage point. This will have to be modified using information from the infusion point. Args: constants: consensus constants being used for this chain sub_slot_start_total_iters: the starting sub-slot iters at the signage point sub-slot sub_slot_iters: sub-slot-iters at the infusion point epoch signage_point_index: signage point index of the block to create sp_iters: sp_iters of the block to create ip_iters: ip_iters of the block to create proof_of_space: proof of space of the block to create slot_cc_challenge: challenge hash at the sp sub-slot farmer_reward_puzzle_hash: where to pay out farmer rewards pool_target: where to pay out pool rewards get_plot_signature: function that returns signature corresponding to plot public key get_pool_signature: function that returns signature corresponding to pool public key signage_point: signage point information (VDFs) timestamp: timestamp to add to the foliage block, if created seed: seed to randomize chain block_generator: transactions to add to the foliage block, if created aggregate_sig: aggregate of all transctions (or infinity element) additions: Coins added in spend_bundle removals: Coins removed in spend_bundle prev_block: previous block (already in chain) from the signage point blocks: dictionary from header hash to SBR of all included SBR finished_sub_slots_input: finished_sub_slots at the signage point Returns: """ if finished_sub_slots_input is None: finished_sub_slots: List[EndOfSubSlotBundle] = [] else: finished_sub_slots = finished_sub_slots_input.copy() overflow: bool = sp_iters > ip_iters total_iters_sp: uint128 = uint128(sub_slot_start_total_iters + sp_iters) is_genesis: bool = prev_block is None new_sub_slot: bool = len(finished_sub_slots) > 0 cc_sp_hash: bytes32 = slot_cc_challenge # Only enters this if statement if we are in testing mode (making VDF proofs here) if signage_point.cc_vdf is not None: assert signage_point.rc_vdf is not None cc_sp_hash = signage_point.cc_vdf.output.get_hash() rc_sp_hash = signage_point.rc_vdf.output.get_hash() else: if new_sub_slot: rc_sp_hash = finished_sub_slots[-1].reward_chain.get_hash() else: if is_genesis: rc_sp_hash = constants.GENESIS_CHALLENGE else: assert prev_block is not None assert blocks is not None curr = prev_block while not curr.first_in_sub_slot: curr = blocks.block_record(curr.prev_hash) assert curr.finished_reward_slot_hashes is not None rc_sp_hash = curr.finished_reward_slot_hashes[-1] signage_point = SignagePoint(None, None, None, None) cc_sp_signature: Optional[G2Element] = get_plot_signature(cc_sp_hash, proof_of_space.plot_public_key) rc_sp_signature: Optional[G2Element] = get_plot_signature(rc_sp_hash, proof_of_space.plot_public_key) assert cc_sp_signature is not None assert rc_sp_signature is not None assert blspy.AugSchemeMPL.verify(proof_of_space.plot_public_key, cc_sp_hash, cc_sp_signature) total_iters = uint128(sub_slot_start_total_iters + ip_iters + (sub_slot_iters if overflow else 0)) rc_block = RewardChainBlockUnfinished( total_iters, signage_point_index, slot_cc_challenge, proof_of_space, signage_point.cc_vdf, cc_sp_signature, signage_point.rc_vdf, rc_sp_signature, ) if additions is None: additions = [] if removals is None: removals = [] (foliage, foliage_transaction_block, transactions_info,) = create_foliage( constants, rc_block, block_generator, aggregate_sig, additions, removals, prev_block, blocks, total_iters_sp, timestamp, farmer_reward_puzzle_hash, pool_target, get_plot_signature, get_pool_signature, seed, ) return UnfinishedBlock( finished_sub_slots, rc_block, signage_point.cc_proof, signage_point.rc_proof, foliage, foliage_transaction_block, transactions_info, block_generator.program if block_generator else None, block_generator.block_height_list() if block_generator else [], )
async def spend_coin(self, coin: CoinWrapper, pushtx=True, **kwargs): """Given a coin object, invoke it on the blockchain, either as a standard coin if no arguments are given or with custom arguments in args=""" amt = 1 if 'amt' in kwargs: amt = kwargs['amt'] delegated_puzzle_solution = None if not 'args' in kwargs: target_puzzle_hash = self.puzzle_hash # Allow the user to 'give this much chia' to another user. if 'to' in kwargs: target_puzzle_hash = kwargs['to'].puzzle_hash # Automatic arguments from the user's intention. if not 'custom_conditions' in kwargs: solution_list = [[ ConditionOpcode.CREATE_COIN, target_puzzle_hash, amt ]] else: solution_list = kwargs['custom_conditions'] if 'remain' in kwargs: remainer = kwargs['remain'] remain_amt = coin.amount - amt if isinstance(remainer, ContractWrapper): solution_list.append([ ConditionOpcode.CREATE_COIN, remainer.puzzle_hash(), remain_amt ]) elif isinstance(remainer, Wallet): solution_list.append([ ConditionOpcode.CREATE_COIN, remainer.puzzle_hash, remain_amt ]) else: raise ValueError( "remainer is not a wallet or a smart coin") delegated_puzzle_solution = Program.to((1, solution_list)) # Solution is the solution for the old coin. solution = Program.to([[], delegated_puzzle_solution, []]) else: delegated_puzzle_solution = Program.to(kwargs['args']) solution = delegated_puzzle_solution solution_for_coin = CoinSpend( coin.as_coin(), coin.puzzle(), solution, ) # The reason this use of sign_coin_spends exists is that it correctly handles # the signing for non-standard coins. I don't fully understand the difference but # this definitely does the right thing. try: spend_bundle = await sign_coin_spends( [solution_for_coin], self.pk_to_sk, DEFAULT_CONSTANTS.AGG_SIG_ME_ADDITIONAL_DATA, DEFAULT_CONSTANTS.MAX_BLOCK_COST_CLVM) except: spend_bundle = SpendBundle( [solution_for_coin], G2Element(), ) if pushtx: pushed = await self.parent.push_tx(spend_bundle) return SpendResult(pushed) else: return spend_bundle
async def create_absorb_transaction( node_rpc_client: FullNodeRpcClient, farmer_record: FarmerRecord, peak_height: uint32, reward_coin_records: List[CoinRecord], genesis_challenge: bytes32, ) -> Optional[SpendBundle]: singleton_state_tuple: Optional[Tuple[ CoinSolution, PoolState, PoolState]] = await get_singleton_state(node_rpc_client, farmer_record.launcher_id, farmer_record, peak_height, 0, genesis_challenge) if singleton_state_tuple is None: log.info(f"Invalid singleton {farmer_record.launcher_id}.") return None last_solution, last_state, last_state_2 = singleton_state_tuple # Here the buried state is equivalent to the latest state, because we use 0 as the security_threshold assert last_state == last_state_2 if last_state.state == PoolSingletonState.SELF_POOLING: log.info( f"Don't try to absorb from former farmer {farmer_record.launcher_id}." ) return None launcher_coin_record: Optional[ CoinRecord] = await node_rpc_client.get_coin_record_by_name( farmer_record.launcher_id) assert launcher_coin_record is not None all_spends: List[CoinSolution] = [] 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), genesis_challenge) if pool_parent == reward_coin_record.coin.parent_coin_info: found_block_index = uint32(block_index) if not found_block_index: # The puzzle does not allow spending coins that are not a coinbase reward log.info( f"Received reward {reward_coin_record.coin} that is not a pool reward." ) continue absorb_spend: List[CoinSolution] = create_absorb_spend( last_solution, last_state, launcher_coin_record.coin, found_block_index, genesis_challenge, farmer_record.delay_time, farmer_record.delay_puzzle_hash, ) last_solution = absorb_spend[0] all_spends += absorb_spend # TODO(pool): handle the case where the cost exceeds the size of the block # TODO(pool): If you want to add a fee, you should do the following: # - only absorb one reward at a time # - spend the coin that you are receiving in the same spend bundle that it is created # - create an output with slightly less XCH, to yourself. for example, 1.7499 XCH # - The remaining value will automatically be used as a fee if len(all_spends) == 0: return None return SpendBundle(all_spends, G2Element())
async def validate_block_body( constants: ConsensusConstants, sub_blocks: BlockchainInterface, block_store: BlockStore, coin_store: CoinStore, peak: Optional[SubBlockRecord], block: Union[FullBlock, UnfinishedBlock], height: uint32, cached_cost_result: Optional[CostResult] = None, fork_point_with_peak: Optional[uint32] = None, ) -> Optional[Err]: """ This assumes the header block has been completely validated. Validates the transactions and body of the block. Returns None if everything validates correctly, or an Err if something does not validate. """ if isinstance(block, FullBlock): assert height == block.height prev_transaction_block_height: uint32 = uint32(0) # 1. For non block sub-blocks, foliage block, transaction filter, transactions info, and generator must be empty # If it is a sub block but not a block, there is no body to validate. Check that all fields are None if block.foliage_sub_block.foliage_block_hash is None: if (block.foliage_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 return None # This means the sub-block is valid # 2. For blocks, foliage block, transaction filter, transactions info must not be empty if block.foliage_block is None or block.foliage_block.filter_hash is None or block.transactions_info is None: return Err.IS_BLOCK_BUT_NO_DATA # 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_block.transactions_info_hash != std_hash( block.transactions_info): return Err.INVALID_TRANSACTIONS_INFO_HASH # 4. The foliage block hash in the foliage sub block must match the foliage block if block.foliage_sub_block.foliage_block_hash != std_hash( block.foliage_block): return Err.INVALID_FOLIAGE_BLOCK_HASH # 5. The prev generators root must be valid # TODO(straya): implement prev generators # 6. The generator root must be the tree-hash of the generator (or zeroes if no generator) if block.transactions_generator is not None: if block.transactions_generator.get_tree_hash( ) != block.transactions_info.generator_root: return Err.INVALID_TRANSACTIONS_GENERATOR_ROOT else: if block.transactions_info.generator_root != bytes([0] * 32): return Err.INVALID_TRANSACTIONS_GENERATOR_ROOT # 7. The reward claims must be valid for the previous sub-blocks, and current block fees if height > 0: # Add reward claims for all sub-blocks from the prev prev block, until the prev block (including the latter) prev_block = sub_blocks.sub_block_record( block.foliage_block.prev_block_hash) prev_transaction_block_height = prev_block.height assert prev_block.fees is not None pool_coin = create_pool_coin( prev_block.height, prev_block.pool_puzzle_hash, calculate_pool_reward(prev_block.height), ) farmer_coin = create_farmer_coin( prev_block.height, prev_block.farmer_puzzle_hash, uint64( calculate_base_farmer_reward(prev_block.height) + prev_block.fees), ) # 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_block.height > 0: curr_sb = sub_blocks.sub_block_record(prev_block.prev_hash) while not curr_sb.is_block: expected_reward_coins.add( create_pool_coin( curr_sb.height, curr_sb.pool_puzzle_hash, calculate_pool_reward(curr_sb.height), )) expected_reward_coins.add( create_farmer_coin( curr_sb.height, curr_sb.farmer_puzzle_hash, calculate_base_farmer_reward(curr_sb.height), )) curr_sb = sub_blocks.sub_block_record(curr_sb.prev_hash) if set(block.transactions_info.reward_claims_incorporated ) != expected_reward_coins: return Err.INVALID_REWARD_COINS removals: List[bytes32] = [] coinbase_additions: List[Coin] = list(expected_reward_coins) additions: List[Coin] = [] announcements: List[Announcement] = [] 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 if block.transactions_generator is not None: # Get List of names removed, puzzles hashes for removed coins and conditions crated if cached_cost_result is not None: result: CostResult = cached_cost_result else: result = calculate_cost_of_program( block.transactions_generator, constants.CLVM_COST_RATIO_CONSTANT) cost = result.cost npc_list = result.npc_list # 8. Check that cost <= MAX_BLOCK_COST_CLVM if cost > constants.MAX_BLOCK_COST_CLVM: return Err.BLOCK_COST_EXCEEDS_MAX if result.error is not None: return Err(result.error) 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) announcements = announcements_for_npc(npc_list) # 9. Check that the correct cost is in the transactions info if block.transactions_info.cost != cost: return Err.INVALID_BLOCK_COST 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 # 11. Validate addition and removal roots root_error = validate_block_merkle_roots( block.foliage_block.additions_root, block.foliage_block.removals_root, additions + coinbase_additions, removals, ) if root_error: return root_error # 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_block.filter_hash: return Err.INVALID_TRANSACTIONS_FILTER_HASH # 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 # 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 # 15. Check if removals exist and were not previously spent. (unspent_db + diff_store + this_block) if peak is None or height == 0: fork_sub_h: int = -1 elif fork_point_with_peak is not None: fork_sub_h = fork_point_with_peak else: fork_sub_h = find_fork_point_in_chain( sub_blocks, peak, sub_blocks.sub_block_record(block.prev_header_hash)) if fork_sub_h == -1: coin_store_reorg_height = -1 else: last_sb_in_common = await sub_blocks.get_sub_block_from_db( sub_blocks.height_to_hash(uint32(fork_sub_h))) assert last_sb_in_common is not None coin_store_reorg_height = last_sb_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: curr: Optional[FullBlock] = await block_store.get_full_block( block.prev_header_hash) assert curr is not None while curr.height > fork_sub_h: removals_in_curr, additions_in_curr = curr.tx_removals_and_additions( ) 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 = await block_store.get_full_block(curr.prev_header_hash) 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, False, block.foliage_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 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 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_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 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 fees = removed - added assert_fee_sum: uint64 = uint64(0) for npc in npc_list: if ConditionOpcode.ASSERT_FEE in npc.condition_dict: fee_list: List[ConditionVarPair] = npc.condition_dict[ ConditionOpcode.ASSERT_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.ASSERT_FEE_CONDITION_FAILED # 18. 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 # 19. 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 # 20. Verify conditions # create hash_key list for aggsig check pairs_pks = [] pairs_msgs = [] for npc in npc_list: unspent = removal_coin_records[npc.coin_name] assert height is not None unspent = removal_coin_records[npc.coin_name] error = blockchain_check_conditions_dict( unspent, announcements, npc.condition_dict, prev_transaction_block_height, block.foliage_block.timestamp, ) if error: return error for pk, m in pkm_pairs_for_conditions_dict(npc.condition_dict, npc.coin_name): pairs_pks.append(pk) pairs_msgs.append(m) # 21. 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 if len(pairs_pks) == 0: if len( pairs_msgs ) != 0 or block.transactions_info.aggregated_signature != G2Element.infinity( ): return Err.BAD_AGGREGATE_SIGNATURE else: # noinspection PyTypeChecker validates = AugSchemeMPL.aggregate_verify( pairs_pks, pairs_msgs, block.transactions_info.aggregated_signature) if not validates: return Err.BAD_AGGREGATE_SIGNATURE return None
import json import unittest from blspy import G2Element from chia.types.spend_bundle import SpendBundle BLANK_SPEND_BUNDLE = SpendBundle(coin_spends=[], aggregated_signature=G2Element()) NULL_SIGNATURE = "0xc" + "0" * 191 class TestStructStream(unittest.TestCase): def test_from_json_legacy(self): JSON = (""" { "coin_solutions": [], "aggregated_signature": "%s" } """ % NULL_SIGNATURE) spend_bundle = SpendBundle.from_json_dict(json.loads(JSON)) json_1 = json.loads(JSON) json_2 = spend_bundle.to_json_dict(include_legacy_keys=True, exclude_modern_keys=True) assert json_1 == json_2 def test_from_json_new(self): JSON = (""" { "coin_spends": [], "aggregated_signature": "%s"
async def test_singleton_top_layer(self): try: # START TESTS # Generate starting info key_lookup = KeyTool() pk: G1Element = public_key_for_index(1, key_lookup) starting_puzzle: Program = p2_delegated_puzzle_or_hidden_puzzle.puzzle_for_pk( pk) # noqa adapted_puzzle: Program = singleton_top_layer.adapt_inner_to_singleton( starting_puzzle) # noqa adapted_puzzle_hash: bytes32 = adapted_puzzle.get_tree_hash() # Get our starting standard coin created START_AMOUNT: uint64 = 1023 sim = await SpendSim.create() sim_client = SimClient(sim) await sim.farm_block(starting_puzzle.get_tree_hash()) starting_coin: Coin = await sim_client.get_coin_records_by_puzzle_hash( starting_puzzle.get_tree_hash()) starting_coin = starting_coin[0].coin comment: List[Tuple[str, str]] = [("hello", "world")] # LAUNCHING # Try to create an even singleton (driver test) try: conditions, launcher_coinsol = singleton_top_layer.launch_conditions_and_coinsol( # noqa starting_coin, adapted_puzzle, comment, (START_AMOUNT - 1)) raise AssertionError("This should fail due to an even amount") except ValueError as msg: assert str( msg) == "Coin amount cannot be even. Subtract one mojo." conditions, launcher_coinsol = singleton_top_layer.launch_conditions_and_coinsol( # noqa starting_coin, adapted_puzzle, comment, START_AMOUNT) # Creating solution for standard transaction delegated_puzzle: Program = p2_conditions.puzzle_for_conditions( conditions) # noqa full_solution: Program = p2_delegated_puzzle_or_hidden_puzzle.solution_for_conditions( conditions) # noqa starting_coinsol = CoinSpend( starting_coin, starting_puzzle, full_solution, ) await self.make_and_spend_bundle( sim, sim_client, starting_coin, delegated_puzzle, [starting_coinsol, launcher_coinsol], ) # EVE singleton_eve: Coin = (await sim.all_non_reward_coins())[0] launcher_coin: Coin = singleton_top_layer.generate_launcher_coin( starting_coin, START_AMOUNT, ) launcher_id: bytes32 = launcher_coin.name() # This delegated puzzle just recreates the coin exactly delegated_puzzle: Program = Program.to(( 1, [[ ConditionOpcode.CREATE_COIN, adapted_puzzle_hash, singleton_eve.amount, ]], )) inner_solution: Program = Program.to([[], delegated_puzzle, []]) # Generate the lineage proof we will need from the launcher coin lineage_proof: LineageProof = singleton_top_layer.lineage_proof_for_coinsol( launcher_coinsol) # noqa puzzle_reveal: Program = singleton_top_layer.puzzle_for_singleton( launcher_id, adapted_puzzle, ) full_solution: Program = singleton_top_layer.solution_for_singleton( lineage_proof, singleton_eve.amount, inner_solution, ) singleton_eve_coinsol = CoinSpend( singleton_eve, puzzle_reveal, full_solution, ) await self.make_and_spend_bundle( sim, sim_client, singleton_eve, delegated_puzzle, [singleton_eve_coinsol], ) # POST-EVE singleton: Coin = (await sim.all_non_reward_coins())[0] # Same delegated_puzzle / inner_solution. We're just recreating ourself lineage_proof: LineageProof = singleton_top_layer.lineage_proof_for_coinsol( singleton_eve_coinsol) # noqa # Same puzzle_reveal too full_solution: Program = singleton_top_layer.solution_for_singleton( lineage_proof, singleton.amount, inner_solution, ) singleton_coinsol = CoinSpend( singleton, puzzle_reveal, full_solution, ) await self.make_and_spend_bundle( sim, sim_client, singleton, delegated_puzzle, [singleton_coinsol], ) # CLAIM A P2_SINGLETON singleton_child: Coin = (await sim.all_non_reward_coins())[0] p2_singleton_puz: Program = singleton_top_layer.pay_to_singleton_puzzle( launcher_id) p2_singleton_ph: bytes32 = p2_singleton_puz.get_tree_hash() await sim.farm_block(p2_singleton_ph) p2_singleton_coin: Coin = await sim_client.get_coin_records_by_puzzle_hash( p2_singleton_ph) p2_singleton_coin = p2_singleton_coin[0].coin assertion, announcement, claim_coinsol = singleton_top_layer.claim_p2_singleton( p2_singleton_coin, adapted_puzzle_hash, launcher_id, ) delegated_puzzle: Program = Program.to(( 1, [ [ ConditionOpcode.CREATE_COIN, adapted_puzzle_hash, singleton_eve.amount ], assertion, announcement, ], )) inner_solution: Program = Program.to([[], delegated_puzzle, []]) lineage_proof: LineageProof = singleton_top_layer.lineage_proof_for_coinsol( singleton_coinsol) puzzle_reveal: Program = singleton_top_layer.puzzle_for_singleton( launcher_id, adapted_puzzle, ) full_solution: Program = singleton_top_layer.solution_for_singleton( lineage_proof, singleton_eve.amount, inner_solution, ) singleton_claim_coinsol = CoinSpend( singleton_child, puzzle_reveal, full_solution, ) await self.make_and_spend_bundle( sim, sim_client, singleton_child, delegated_puzzle, [singleton_claim_coinsol, claim_coinsol]) # CLAIM A P2_SINGLETON_OR_DELAYED singleton_child: Coin = (await sim.all_non_reward_coins())[0] DELAY_TIME: uint64 = 1 DELAY_PH: bytes32 = adapted_puzzle_hash p2_singleton_puz: Program = singleton_top_layer.pay_to_singleton_or_delay_puzzle( launcher_id, DELAY_TIME, DELAY_PH, ) p2_singleton_ph: bytes32 = p2_singleton_puz.get_tree_hash() ARBITRARY_AMOUNT: uint64 = 250000000000 await sim.farm_block(p2_singleton_ph) p2_singleton_coin: Coin = await sim_client.get_coin_records_by_puzzle_hash( p2_singleton_ph) p2_singleton_coin = sorted(p2_singleton_coin, key=lambda x: x.coin.amount)[0].coin assertion, announcement, claim_coinsol = singleton_top_layer.claim_p2_singleton( p2_singleton_coin, adapted_puzzle_hash, launcher_id, DELAY_TIME, DELAY_PH, ) delegated_puzzle: Program = Program.to(( 1, [ [ ConditionOpcode.CREATE_COIN, adapted_puzzle_hash, singleton_eve.amount ], assertion, announcement, ], )) inner_solution: Program = Program.to([[], delegated_puzzle, []]) lineage_proof: LineageProof = singleton_top_layer.lineage_proof_for_coinsol( singleton_claim_coinsol) puzzle_reveal: Program = singleton_top_layer.puzzle_for_singleton( launcher_id, adapted_puzzle, ) full_solution: Program = singleton_top_layer.solution_for_singleton( lineage_proof, singleton_eve.amount, inner_solution, ) delay_claim_coinsol = CoinSpend( singleton_child, puzzle_reveal, full_solution, ) # Save the height so we can rewind after this save_height: uint64 = ( sim.get_height() ) # The last coin solution before this point is singleton_claim_coinsol await self.make_and_spend_bundle( sim, sim_client, singleton_child, delegated_puzzle, [delay_claim_coinsol, claim_coinsol]) # TRY TO SPEND AWAY TOO SOON (Negative Test) await sim.rewind(save_height) to_delay_ph_coinsol: CoinSpend = singleton_top_layer.spend_to_delayed_puzzle( p2_singleton_coin, ARBITRARY_AMOUNT, launcher_id, DELAY_TIME, DELAY_PH, ) result, error = await sim_client.push_tx( SpendBundle([to_delay_ph_coinsol], G2Element())) assert error == Err.ASSERT_SECONDS_RELATIVE_FAILED # SPEND TO DELAYED PUZZLE HASH await sim.rewind(save_height) sim.pass_time(10000005) sim.pass_blocks(100) await sim_client.push_tx( SpendBundle([to_delay_ph_coinsol], G2Element())) # CREATE MULTIPLE ODD CHILDREN (Negative Test) singleton_child: Coin = (await sim.all_non_reward_coins())[0] delegated_puzzle: Program = Program.to(( 1, [ [ConditionOpcode.CREATE_COIN, adapted_puzzle_hash, 3], [ConditionOpcode.CREATE_COIN, adapted_puzzle_hash, 7], ], )) inner_solution: Program = Program.to([[], delegated_puzzle, []]) lineage_proof: LineageProof = singleton_top_layer.lineage_proof_for_coinsol( singleton_claim_coinsol) puzzle_reveal: Program = singleton_top_layer.puzzle_for_singleton( launcher_id, adapted_puzzle, ) full_solution: Program = singleton_top_layer.solution_for_singleton( lineage_proof, singleton_child.amount, inner_solution) multi_odd_coinsol = CoinSpend( singleton_child, puzzle_reveal, full_solution, ) await self.make_and_spend_bundle( sim, sim_client, singleton_child, delegated_puzzle, [multi_odd_coinsol], ex_error=Err.GENERATOR_RUNTIME_ERROR, fail_msg="Too many odd children were allowed", ) # CREATE NO ODD CHILDREN (Negative Test) delegated_puzzle: Program = Program.to(( 1, [ [ConditionOpcode.CREATE_COIN, adapted_puzzle_hash, 4], [ConditionOpcode.CREATE_COIN, adapted_puzzle_hash, 10], ], )) inner_solution: Program = Program.to([[], delegated_puzzle, []]) lineage_proof: LineageProof = singleton_top_layer.lineage_proof_for_coinsol( singleton_claim_coinsol) puzzle_reveal: Program = singleton_top_layer.puzzle_for_singleton( launcher_id, adapted_puzzle, ) full_solution: Program = singleton_top_layer.solution_for_singleton( lineage_proof, singleton_child.amount, inner_solution) no_odd_coinsol = CoinSpend( singleton_child, puzzle_reveal, full_solution, ) await self.make_and_spend_bundle( sim, sim_client, singleton_child, delegated_puzzle, [no_odd_coinsol], ex_error=Err.GENERATOR_RUNTIME_ERROR, fail_msg="Need at least one odd child", ) # ATTEMPT TO CREATE AN EVEN SINGLETON (Negative test) await sim.rewind(save_height) delegated_puzzle: Program = Program.to(( 1, [ [ ConditionOpcode.CREATE_COIN, singleton_child.puzzle_hash, 2, ], [ConditionOpcode.CREATE_COIN, adapted_puzzle_hash, 1], ], )) inner_solution: Program = Program.to([[], delegated_puzzle, []]) lineage_proof: LineageProof = singleton_top_layer.lineage_proof_for_coinsol( singleton_claim_coinsol) puzzle_reveal: Program = singleton_top_layer.puzzle_for_singleton( launcher_id, adapted_puzzle, ) full_solution: Program = singleton_top_layer.solution_for_singleton( lineage_proof, singleton_child.amount, inner_solution) singleton_even_coinsol = CoinSpend( singleton_child, puzzle_reveal, full_solution, ) await self.make_and_spend_bundle( sim, sim_client, singleton_child, delegated_puzzle, [singleton_even_coinsol], ) # Now try a perfectly innocent spend evil_coin: Coin = (await sim.all_non_reward_coins())[0] delegated_puzzle: Program = Program.to(( 1, [ [ ConditionOpcode.CREATE_COIN, adapted_puzzle_hash, 1, ], ], )) inner_solution: Program = Program.to([[], delegated_puzzle, []]) lineage_proof: LineageProof = singleton_top_layer.lineage_proof_for_coinsol( singleton_even_coinsol) # noqa puzzle_reveal: Program = singleton_top_layer.puzzle_for_singleton( launcher_id, adapted_puzzle, ) full_solution: Program = singleton_top_layer.solution_for_singleton( lineage_proof, 1, inner_solution, ) evil_coinsol = CoinSpend( evil_coin, puzzle_reveal, full_solution, ) await self.make_and_spend_bundle( sim, sim_client, evil_coin, delegated_puzzle, [evil_coinsol], ex_error=Err.ASSERT_MY_COIN_ID_FAILED, fail_msg="This coin is even!", ) # MELTING # Remember, we're still spending singleton_child await sim.rewind(save_height) conditions = [ singleton_top_layer.MELT_CONDITION, [ ConditionOpcode.CREATE_COIN, adapted_puzzle_hash, (singleton_child.amount - 1), ], ] delegated_puzzle: Program = p2_conditions.puzzle_for_conditions( conditions) inner_solution: Program = p2_delegated_puzzle_or_hidden_puzzle.solution_for_conditions( conditions) lineage_proof: LineageProof = singleton_top_layer.lineage_proof_for_coinsol( singleton_claim_coinsol) puzzle_reveal: Program = singleton_top_layer.puzzle_for_singleton( launcher_id, adapted_puzzle, ) full_solution: Program = singleton_top_layer.solution_for_singleton( lineage_proof, singleton_child.amount, inner_solution) melt_coinsol = CoinSpend( singleton_child, puzzle_reveal, full_solution, ) await self.make_and_spend_bundle( sim, sim_client, singleton_child, delegated_puzzle, [melt_coinsol], ) melted_coin: Coin = (await sim.all_non_reward_coins())[0] assert melted_coin.puzzle_hash == adapted_puzzle_hash finally: await sim.close()
def signature_for_coinbase(coin: Coin, pool_private_key: PrivateKey): # noinspection PyTypeChecker return G2Element.from_bytes(bytes(AugSchemeMPL.sign(pool_private_key, bytes(coin))))
async def test_cat_mod(self, setup_sim): sim, sim_client = setup_sim try: tail = Program.to([]) checker_solution = Program.to([]) cat_puzzle: Program = construct_cat_puzzle(CAT_MOD, tail.get_tree_hash(), acs) cat_ph: bytes32 = cat_puzzle.get_tree_hash() await sim.farm_block(cat_ph) starting_coin: Coin = (await sim_client.get_coin_records_by_puzzle_hash(cat_ph))[0].coin # Testing the eve spend await self.do_spend( sim, sim_client, tail, [starting_coin], [NO_LINEAGE_PROOF], [ Program.to( [ [51, acs.get_tree_hash(), starting_coin.amount - 3, [b"memo"]], [51, acs.get_tree_hash(), 1], [51, acs.get_tree_hash(), 2], [51, 0, -113, tail, checker_solution], ] ) ], (MempoolInclusionStatus.SUCCESS, None), limitations_solutions=[checker_solution], cost_str="Eve Spend", ) # There's 4 total coins at this point. A farming reward and the three children of the spend above. # Testing a combination of two coins: List[Coin] = [ record.coin for record in (await sim_client.get_coin_records_by_puzzle_hash(cat_ph, include_spent_coins=False)) ] coins = [coins[0], coins[1]] await self.do_spend( sim, sim_client, tail, coins, [NO_LINEAGE_PROOF] * 2, [ Program.to( [ [51, acs.get_tree_hash(), coins[0].amount + coins[1].amount], [51, 0, -113, tail, checker_solution], ] ), Program.to([[51, 0, -113, tail, checker_solution]]), ], (MempoolInclusionStatus.SUCCESS, None), limitations_solutions=[checker_solution] * 2, cost_str="Two CATs", ) # Testing a combination of three coins = [ record.coin for record in (await sim_client.get_coin_records_by_puzzle_hash(cat_ph, include_spent_coins=False)) ] total_amount: uint64 = uint64(sum([c.amount for c in coins])) await self.do_spend( sim, sim_client, tail, coins, [NO_LINEAGE_PROOF] * 3, [ Program.to( [ [51, acs.get_tree_hash(), total_amount], [51, 0, -113, tail, checker_solution], ] ), Program.to([[51, 0, -113, tail, checker_solution]]), Program.to([[51, 0, -113, tail, checker_solution]]), ], (MempoolInclusionStatus.SUCCESS, None), limitations_solutions=[checker_solution] * 3, cost_str="Three CATs", ) # Spend with a standard lineage proof parent_coin: Coin = coins[0] # The first one is the one we didn't light on fire _, curried_args = cat_puzzle.uncurry() _, _, innerpuzzle = curried_args.as_iter() lineage_proof = LineageProof(parent_coin.parent_coin_info, innerpuzzle.get_tree_hash(), parent_coin.amount) await self.do_spend( sim, sim_client, tail, [(await sim_client.get_coin_records_by_puzzle_hash(cat_ph, include_spent_coins=False))[0].coin], [lineage_proof], [Program.to([[51, acs.get_tree_hash(), total_amount]])], (MempoolInclusionStatus.SUCCESS, None), reveal_limitations_program=False, cost_str="Standard Lineage Check", ) # Melt some value await self.do_spend( sim, sim_client, tail, [(await sim_client.get_coin_records_by_puzzle_hash(cat_ph, include_spent_coins=False))[0].coin], [NO_LINEAGE_PROOF], [ Program.to( [ [51, acs.get_tree_hash(), total_amount - 1], [51, 0, -113, tail, checker_solution], ] ) ], (MempoolInclusionStatus.SUCCESS, None), extra_deltas=[-1], limitations_solutions=[checker_solution], cost_str="Melting Value", ) # Mint some value temp_p = Program.to(1) temp_ph: bytes32 = temp_p.get_tree_hash() await sim.farm_block(temp_ph) acs_coin: Coin = (await sim_client.get_coin_records_by_puzzle_hash(temp_ph, include_spent_coins=False))[ 0 ].coin acs_bundle = SpendBundle( [ CoinSpend( acs_coin, temp_p, Program.to([]), ) ], G2Element(), ) await self.do_spend( sim, sim_client, tail, [(await sim_client.get_coin_records_by_puzzle_hash(cat_ph, include_spent_coins=False))[0].coin], [NO_LINEAGE_PROOF], [ Program.to( [ [51, acs.get_tree_hash(), total_amount], [51, 0, -113, tail, checker_solution], ] ) ], # We subtracted 1 last time so it's normal now (MempoolInclusionStatus.SUCCESS, None), extra_deltas=[1], additional_spends=[acs_bundle], limitations_solutions=[checker_solution], cost_str="Mint Value", ) finally: await sim.close()
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 # TODO(OzChiaPool): fix ### Commented these out because the puzzles don't exist yet # 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
async def test_everything_with_signature(self, setup_sim): sim, sim_client = setup_sim try: sk = PrivateKey.from_bytes(secret_exponent_for_index(1).to_bytes(32, "big")) tail: Program = EverythingWithSig.construct([Program.to(sk.get_g1())]) checker_solution: Program = EverythingWithSig.solve([], {}) cat_puzzle: Program = construct_cat_puzzle(CAT_MOD, tail.get_tree_hash(), acs) cat_ph: bytes32 = cat_puzzle.get_tree_hash() await sim.farm_block(cat_ph) # Test eve spend # We don't sign any message data because CLVM 0 translates to b'' apparently starting_coin: Coin = (await sim_client.get_coin_records_by_puzzle_hash(cat_ph))[0].coin signature: G2Element = AugSchemeMPL.sign( sk, (starting_coin.name() + sim.defaults.AGG_SIG_ME_ADDITIONAL_DATA) ) await self.do_spend( sim, sim_client, tail, [starting_coin], [NO_LINEAGE_PROOF], [ Program.to( [ [51, acs.get_tree_hash(), starting_coin.amount], [51, 0, -113, tail, checker_solution], ] ) ], (MempoolInclusionStatus.SUCCESS, None), limitations_solutions=[checker_solution], signatures=[signature], cost_str="Signature Issuance", ) # Test melting value coin: Coin = (await sim_client.get_coin_records_by_puzzle_hash(cat_ph, include_spent_coins=False))[0].coin signature = AugSchemeMPL.sign( sk, (int_to_bytes(-1) + coin.name() + sim.defaults.AGG_SIG_ME_ADDITIONAL_DATA) ) await self.do_spend( sim, sim_client, tail, [coin], [NO_LINEAGE_PROOF], [ Program.to( [ [51, acs.get_tree_hash(), coin.amount - 1], [51, 0, -113, tail, checker_solution], ] ) ], (MempoolInclusionStatus.SUCCESS, None), extra_deltas=[-1], limitations_solutions=[checker_solution], signatures=[signature], cost_str="Signature Melt", ) # Test minting value coin = (await sim_client.get_coin_records_by_puzzle_hash(cat_ph, include_spent_coins=False))[0].coin signature = AugSchemeMPL.sign(sk, (int_to_bytes(1) + coin.name() + sim.defaults.AGG_SIG_ME_ADDITIONAL_DATA)) # Need something to fund the minting temp_p = Program.to(1) temp_ph: bytes32 = temp_p.get_tree_hash() await sim.farm_block(temp_ph) acs_coin: Coin = (await sim_client.get_coin_records_by_puzzle_hash(temp_ph, include_spent_coins=False))[ 0 ].coin acs_bundle = SpendBundle( [ CoinSpend( acs_coin, temp_p, Program.to([]), ) ], G2Element(), ) await self.do_spend( sim, sim_client, tail, [coin], [NO_LINEAGE_PROOF], [ Program.to( [ [51, acs.get_tree_hash(), coin.amount + 1], [51, 0, -113, tail, checker_solution], ] ) ], (MempoolInclusionStatus.SUCCESS, None), extra_deltas=[1], limitations_solutions=[checker_solution], signatures=[signature], additional_spends=[acs_bundle], cost_str="Signature Mint", ) finally: await sim.close()
async def claim_pool_rewards(self, fee: uint64) -> TransactionRecord: # Search for p2_puzzle_hash coins, and spend them with the singleton if await self.have_unconfirmed_transaction(): raise ValueError( "Cannot claim due to unconfirmed transaction. If this is stuck, delete the unconfirmed transaction." ) unspent_coin_records: List[CoinRecord] = list( await self.wallet_state_manager.coin_store.get_unspent_coins_for_wallet(self.wallet_id) ) if len(unspent_coin_records) == 0: raise ValueError("Nothing to claim, no transactions to p2_singleton_puzzle_hash") farming_rewards: List[TransactionRecord] = await self.wallet_state_manager.tx_store.get_farming_rewards() coin_to_height_farmed: Dict[Coin, uint32] = {} for tx_record in farming_rewards: height_farmed: Optional[uint32] = tx_record.height_farmed( self.wallet_state_manager.constants.GENESIS_CHALLENGE ) assert height_farmed is not None coin_to_height_farmed[tx_record.additions[0]] = height_farmed history: List[Tuple[uint32, CoinSpend]] = await self.get_spend_history() assert len(history) > 0 delayed_seconds, delayed_puzhash = get_delayed_puz_info_from_launcher_spend(history[0][1]) current_state: PoolWalletInfo = await self.get_current_state() last_solution: CoinSpend = history[-1][1] all_spends: List[CoinSpend] = [] total_amount = 0 for coin_record in unspent_coin_records: if coin_record.coin not in coin_to_height_farmed: continue if len(all_spends) >= 100: # Limit the total number of spends, so it fits into the block break absorb_spend: List[CoinSpend] = create_absorb_spend( last_solution, current_state.current, current_state.launcher_coin, coin_to_height_farmed[coin_record.coin], self.wallet_state_manager.constants.GENESIS_CHALLENGE, delayed_seconds, delayed_puzhash, ) last_solution = absorb_spend[0] all_spends += absorb_spend total_amount += coin_record.coin.amount self.log.info( f"Farmer coin: {coin_record.coin} {coin_record.coin.name()} {coin_to_height_farmed[coin_record.coin]}" ) if len(all_spends) == 0: raise ValueError("Nothing to claim, no unspent coinbase rewards") # No signatures are required to absorb spend_bundle: SpendBundle = SpendBundle(all_spends, G2Element()) absorb_transaction: TransactionRecord = TransactionRecord( confirmed_at_height=uint32(0), created_at_time=uint64(int(time.time())), to_puzzle_hash=current_state.current.target_puzzle_hash, amount=uint64(total_amount), fee_amount=fee, confirmed=False, sent=uint32(0), spend_bundle=spend_bundle, additions=spend_bundle.additions(), removals=spend_bundle.removals(), wallet_id=uint32(self.wallet_id), sent_to=[], memos=[], trade_id=None, type=uint32(TransactionType.OUTGOING_TX.value), name=spend_bundle.name(), ) await self.wallet_state_manager.add_pending_transaction(absorb_transaction) return absorb_transaction
async def main() -> None: rpc_port: uint16 = uint16(8555) self_hostname = "localhost" path = DEFAULT_ROOT_PATH config = load_config(path, "config.yaml") client = await FullNodeRpcClient.create(self_hostname, rpc_port, path, config) try: farmer_prefarm = ( await client.get_block_record_by_height(1)).reward_claims_incorporated[1] pool_prefarm = ( await client.get_block_record_by_height(1)).reward_claims_incorporated[0] pool_amounts = int(calculate_pool_reward(uint32(0)) / 2) farmer_amounts = int(calculate_base_farmer_reward(uint32(0)) / 2) print(farmer_prefarm.amount, farmer_amounts) assert farmer_amounts == farmer_prefarm.amount // 2 assert pool_amounts == pool_prefarm.amount // 2 address1 = "xch1rdatypul5c642jkeh4yp933zu3hw8vv8tfup8ta6zfampnyhjnusxdgns6" # Key 1 address2 = "xch1duvy5ur5eyj7lp5geetfg84cj2d7xgpxt7pya3lr2y6ke3696w9qvda66e" # Key 2 ph1 = decode_puzzle_hash(address1) ph2 = decode_puzzle_hash(address2) p_farmer_2 = Program.to( binutils.assemble( f"(q . ((51 0x{ph1.hex()} {farmer_amounts}) (51 0x{ph2.hex()} {farmer_amounts})))" )) p_pool_2 = Program.to( binutils.assemble( f"(q . ((51 0x{ph1.hex()} {pool_amounts}) (51 0x{ph2.hex()} {pool_amounts})))" )) print(f"Ph1: {ph1.hex()}") print(f"Ph2: {ph2.hex()}") assert ph1.hex( ) == "1b7ab2079fa635554ad9bd4812c622e46ee3b1875a7813afba127bb0cc9794f9" assert ph2.hex( ) == "6f184a7074c925ef8688ce56941eb8929be320265f824ec7e351356cc745d38a" p_solution = Program.to(binutils.assemble("()")) sb_farmer = SpendBundle( [CoinSolution(farmer_prefarm, p_farmer_2, p_solution)], G2Element()) sb_pool = SpendBundle( [CoinSolution(pool_prefarm, p_pool_2, p_solution)], G2Element()) print("\n\n\nConditions") print_conditions(sb_pool) print("\n\n\n") print("Farmer to spend") print(sb_pool) print(sb_farmer) print("\n\n\n") # res = await client.push_tx(sb_farmer) # res = await client.push_tx(sb_pool) # print(res) up = await client.get_coin_records_by_puzzle_hash( farmer_prefarm.puzzle_hash, True) uf = await client.get_coin_records_by_puzzle_hash( pool_prefarm.puzzle_hash, True) print(up) print(uf) finally: client.close()
def test_schemes(): # fmt: off seed = bytes([ 0, 50, 6, 244, 24, 199, 1, 25, 52, 88, 192, 19, 18, 12, 89, 6, 220, 18, 102, 58, 209, 82, 12, 62, 89, 110, 182, 9, 44, 20, 254, 22 ]) # fmt: on msg = bytes([100, 2, 254, 88, 90, 45, 23]) msg2 = bytes([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) sk = BasicSchemeMPL.key_gen(seed) pk = sk.get_g1() assert sk == PrivateKey.from_bytes(bytes(sk)) assert pk == G1Element.from_bytes(bytes(pk)) for Scheme in (BasicSchemeMPL, AugSchemeMPL, PopSchemeMPL): sig = Scheme.sign(sk, msg) assert sig == G2Element.from_bytes(bytes(sig)) assert Scheme.verify(pk, msg, sig) seed = bytes([1]) + seed[1:] sk1 = BasicSchemeMPL.key_gen(seed) pk1 = sk1.get_g1() seed = bytes([2]) + seed[1:] sk2 = BasicSchemeMPL.key_gen(seed) pk2 = sk2.get_g1() for Scheme in (BasicSchemeMPL, AugSchemeMPL, PopSchemeMPL): # Aggregate same message agg_pk = pk1 + pk2 if Scheme is AugSchemeMPL: sig1 = Scheme.sign(sk1, msg, agg_pk) sig2 = Scheme.sign(sk2, msg, agg_pk) else: sig1 = Scheme.sign(sk1, msg) sig2 = Scheme.sign(sk2, msg) agg_sig = Scheme.aggregate([sig1, sig2]) assert Scheme.verify(agg_pk, msg, agg_sig) # Aggregate different message sig1 = Scheme.sign(sk1, msg) sig2 = Scheme.sign(sk2, msg2) agg_sig = Scheme.aggregate([sig1, sig2]) assert Scheme.aggregate_verify([pk1, pk2], [msg, msg2], agg_sig) # Manual pairing calculation and verification if Scheme is AugSchemeMPL: # AugSchemeMPL requires prepending the public key to message aug_msg1 = bytes(pk1) + msg aug_msg2 = bytes(pk2) + msg2 else: aug_msg1 = msg aug_msg2 = msg2 pair1 = pk1.pair(Scheme.g2_from_message(aug_msg1)) pair2 = pk2.pair(Scheme.g2_from_message(aug_msg2)) pair = pair1 * pair2 agg_sig_pair = G1Element.generator().pair(agg_sig) assert pair == agg_sig_pair # HD keys child = Scheme.derive_child_sk(sk1, 123) childU = Scheme.derive_child_sk_unhardened(sk1, 123) childUPk = Scheme.derive_child_pk_unhardened(pk1, 123) sig_child = Scheme.sign(child, msg) assert Scheme.verify(child.get_g1(), msg, sig_child) sigU_child = Scheme.sign(childU, msg) assert Scheme.verify(childUPk, msg, sigU_child)
def signature_for_coinbase(coin: Coin, pool_private_key: PrivateKey): return G2Element.from_bytes( bytes(AugSchemeMPL.sign(pool_private_key, bytes(coin))))
def create_foliage( constants: ConsensusConstants, reward_block_unfinished: RewardChainBlockUnfinished, spend_bundle: Optional[SpendBundle], 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], Optional[SerializedProgram]]: """ 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 spend_bundle: the spend bundle including all transactions 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 solution_program: Optional[SerializedProgram] = None if is_transaction_block: aggregate_sig: G2Element = G2Element() cost = uint64(0) if spend_bundle is not None: solution_program = best_solution_program(spend_bundle) aggregate_sig = spend_bundle.aggregated_signature # Calculate the cost of transactions if solution_program is not None: result: CostResult = calculate_cost_of_program( solution_program, constants.CLVM_COST_RATIO_CONSTANT) cost = result.cost 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 # TODO: prev generators root 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 = solution_program.get_tree_hash( ) if solution_program is not None else bytes32([0] * 32) generator_refs_hash = bytes32([1] * 32) 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, solution_program
def sign_tx(intermediate_sk: PrivateKey, spend_bundle: SpendBundle, use_hardened_keys: bool): """ Takes in an unsigned transaction (called a spend bundle in chia), and a 24 word mnemonic (master sk) and generates the aggregate BLS signature for the transaction. """ # This field is the ADDITIONAL_DATA found in the constants additional_data: bytes = bytes.fromhex( "ccd5bb71183532bff220ba46c268991a3ff07eb358e8255a65c30a2dce0e5fbb") puzzle_hash_to_sk: Dict[bytes32, PrivateKey] = {} if use_hardened_keys: # Change this loop to scan more keys if you have more for i in range(5000): child_sk: PrivateKey = AugSchemeMPL.derive_child_sk( intermediate_sk, i) child_pk: G1Element = child_sk.get_g1() puzzle = puzzle_for_pk(child_pk) puzzle_hash = puzzle.get_tree_hash() puzzle_hash_to_sk[puzzle_hash] = child_sk else: # Change this loop to scan more keys if you have more for i in range(5000): child_sk: PrivateKey = AugSchemeMPL.derive_child_sk_unhardened( intermediate_sk, i) child_pk: G1Element = child_sk.get_g1() puzzle = puzzle_for_pk(child_pk) puzzle_hash = puzzle.get_tree_hash() puzzle_hash_to_sk[puzzle_hash] = child_sk aggregate_signature: G2Element = G2Element() for coin_solution in spend_bundle.coin_solutions: if coin_solution.coin.puzzle_hash not in puzzle_hash_to_sk: print( f"Puzzle hash {coin_solution.coin.puzzle_hash} not found for this key." ) return sk: PrivateKey = puzzle_hash_to_sk[coin_solution.coin.puzzle_hash] synthetic_secret_key: PrivateKey = calculate_synthetic_secret_key( sk, DEFAULT_HIDDEN_PUZZLE_HASH) err, conditions_dict, cost = conditions_dict_for_solution( coin_solution.puzzle_reveal, coin_solution.solution, 11000000000) if err or conditions_dict is None: print( f"Sign transaction failed, con:{conditions_dict}, error: {err}" ) return pk_msgs = pkm_pairs_for_conditions_dict( conditions_dict, bytes(coin_solution.coin.name()), additional_data) assert len(pk_msgs) == 1 _, msg = pk_msgs[0] signature = AugSchemeMPL.sign(synthetic_secret_key, msg) aggregate_signature = AugSchemeMPL.aggregate( [aggregate_signature, signature]) new_spend_bundle = SpendBundle(spend_bundle.coin_solutions, aggregate_signature) print("") print("Signed spend bundle JSON:\n") print(json.dumps(new_spend_bundle.to_json_dict()))
def test_aggregate_verify_zero_items(): assert AugSchemeMPL.aggregate_verify([], [], G2Element())
def test_lifecycle_with_coinstore_as_wallet(): PUZZLE_DB = PuzzleDB() interested_singletons = [] ####### # farm a coin coin_store = CoinStore(int.from_bytes(POOL_REWARD_PREFIX_MAINNET, "big")) now = CoinTimestamp(10012300, 1) DELAY_SECONDS = 86400 DELAY_PUZZLE_HASH = bytes([0] * 32) ####### # spend coin to a singleton additions, removals = spend_coin_to_singleton(PUZZLE_DB, LAUNCHER_PUZZLE, coin_store, now) assert len(list(coin_store.all_unspent_coins())) == 1 new_singletons = find_interesting_singletons(PUZZLE_DB, removals) interested_singletons.extend(new_singletons) assert len(interested_singletons) == 1 SINGLETON_WALLET = interested_singletons[0] ####### # farm a `p2_singleton` pool_reward_puzzle_hash = p2_singleton_puzzle_hash_for_launcher( PUZZLE_DB, SINGLETON_WALLET.launcher_id, SINGLETON_WALLET.launcher_puzzle_hash, DELAY_SECONDS, DELAY_PUZZLE_HASH) farmed_coin = coin_store.farm_coin(pool_reward_puzzle_hash, now) now.seconds += 500 now.height += 1 p2_singleton_coins = filter_p2_singleton(PUZZLE_DB, SINGLETON_WALLET, [farmed_coin]) assert p2_singleton_coins == [farmed_coin] assert len(list(coin_store.all_unspent_coins())) == 2 ####### # now collect the `p2_singleton` using the singleton for coin in p2_singleton_coins: p2_singleton_coin_spend, singleton_conditions = claim_p2_singleton( PUZZLE_DB, SINGLETON_WALLET, coin) coin_spend = SINGLETON_WALLET.coin_spend_for_conditions( PUZZLE_DB, conditions=singleton_conditions) spend_bundle = SpendBundle([coin_spend, p2_singleton_coin_spend], G2Element()) additions, removals = coin_store.update_coin_store_for_spend_bundle( spend_bundle, now, MAX_BLOCK_COST_CLVM) now.seconds += 500 now.height += 1 SINGLETON_WALLET.update_state(PUZZLE_DB, removals) assert len(list(coin_store.all_unspent_coins())) == 1 ####### # farm and collect another `p2_singleton` pool_reward_puzzle_hash = p2_singleton_puzzle_hash_for_launcher( PUZZLE_DB, SINGLETON_WALLET.launcher_id, SINGLETON_WALLET.launcher_puzzle_hash, DELAY_SECONDS, DELAY_PUZZLE_HASH) farmed_coin = coin_store.farm_coin(pool_reward_puzzle_hash, now) now.seconds += 500 now.height += 1 p2_singleton_coins = filter_p2_singleton(PUZZLE_DB, SINGLETON_WALLET, [farmed_coin]) assert p2_singleton_coins == [farmed_coin] assert len(list(coin_store.all_unspent_coins())) == 2 for coin in p2_singleton_coins: p2_singleton_coin_spend, singleton_conditions = claim_p2_singleton( PUZZLE_DB, SINGLETON_WALLET, coin) coin_spend = SINGLETON_WALLET.coin_spend_for_conditions( PUZZLE_DB, conditions=singleton_conditions) spend_bundle = SpendBundle([coin_spend, p2_singleton_coin_spend], G2Element()) additions, removals = coin_store.update_coin_store_for_spend_bundle( spend_bundle, now, MAX_BLOCK_COST_CLVM) now.seconds += 500 now.height += 1 SINGLETON_WALLET.update_state(PUZZLE_DB, removals) assert len(list(coin_store.all_unspent_coins())) == 1 ####### # loan the singleton to a pool # puzzle_for_loan_singleton_to_pool( # pool_puzzle_hash, p2_singleton_puzzle_hash, owner_public_key, pool_reward_prefix, relative_lock_height) # calculate the series owner_public_key = bytes(create_throwaway_pubkey(b"foo")) pool_puzzle_hash = Program.to(bytes( create_throwaway_pubkey(b""))).get_tree_hash() pool_reward_prefix = POOL_REWARD_PREFIX_MAINNET relative_lock_height = 1440 pool_escaping_puzzle = POOL_WAITINGROOM_MOD.curry(pool_puzzle_hash, pool_reward_puzzle_hash, owner_public_key, pool_reward_prefix, relative_lock_height) pool_escaping_puzzle_hash = pool_escaping_puzzle.get_tree_hash() pool_member_puzzle = POOL_MEMBER_MOD.curry( pool_puzzle_hash, pool_reward_puzzle_hash, owner_public_key, pool_reward_prefix, pool_escaping_puzzle_hash, ) pool_member_puzzle_hash = pool_member_puzzle.get_tree_hash() PUZZLE_DB.add_puzzle(pool_escaping_puzzle) PUZZLE_DB.add_puzzle( singleton_puzzle(SINGLETON_WALLET.launcher_id, SINGLETON_WALLET.launcher_puzzle_hash, pool_escaping_puzzle)) PUZZLE_DB.add_puzzle(pool_member_puzzle) full_puzzle = singleton_puzzle(SINGLETON_WALLET.launcher_id, SINGLETON_WALLET.launcher_puzzle_hash, pool_member_puzzle) PUZZLE_DB.add_puzzle(full_puzzle) conditions = [ Program.to([ConditionOpcode.CREATE_COIN, pool_member_puzzle_hash, 1]) ] singleton_coin_spend = SINGLETON_WALLET.coin_spend_for_conditions( PUZZLE_DB, conditions=conditions) spend_bundle = SpendBundle([singleton_coin_spend], G2Element()) additions, removals = coin_store.update_coin_store_for_spend_bundle( spend_bundle, now, MAX_BLOCK_COST_CLVM) assert len(list(coin_store.all_unspent_coins())) == 1 SINGLETON_WALLET.update_state(PUZZLE_DB, removals) ####### # farm a `p2_singleton` pool_reward_puzzle_hash = p2_singleton_puzzle_hash_for_launcher( PUZZLE_DB, SINGLETON_WALLET.launcher_id, SINGLETON_WALLET.launcher_puzzle_hash, DELAY_SECONDS, DELAY_PUZZLE_HASH) farmed_coin = coin_store.farm_coin(pool_reward_puzzle_hash, now) now.seconds += 500 now.height += 1 p2_singleton_coins = filter_p2_singleton(PUZZLE_DB, SINGLETON_WALLET, [farmed_coin]) assert p2_singleton_coins == [farmed_coin] assert len(list(coin_store.all_unspent_coins())) == 2 ####### # now collect the `p2_singleton` for the pool for coin in p2_singleton_coins: p2_singleton_coin_spend, singleton_conditions = claim_p2_singleton( PUZZLE_DB, SINGLETON_WALLET, coin) coin_spend = SINGLETON_WALLET.coin_spend_for_conditions( PUZZLE_DB, pool_member_spend_type="claim-p2-nft", pool_reward_amount=p2_singleton_coin_spend.coin.amount, pool_reward_height=now.height - 1, ) spend_bundle = SpendBundle([coin_spend, p2_singleton_coin_spend], G2Element()) spend_bundle.debug() additions, removals = coin_store.update_coin_store_for_spend_bundle( spend_bundle, now, MAX_BLOCK_COST_CLVM) now.seconds += 500 now.height += 1 SINGLETON_WALLET.update_state(PUZZLE_DB, removals) assert len(list(coin_store.all_unspent_coins())) == 2 ####### # spend the singleton into the "leaving the pool" state coin_spend = SINGLETON_WALLET.coin_spend_for_conditions( PUZZLE_DB, pool_member_spend_type="to-waiting-room", key_value_list=Program.to([("foo", "bar")])) spend_bundle = SpendBundle([coin_spend], G2Element()) additions, removals = coin_store.update_coin_store_for_spend_bundle( spend_bundle, now, MAX_BLOCK_COST_CLVM) now.seconds += 500 now.height += 1 change_count = SINGLETON_WALLET.update_state(PUZZLE_DB, removals) assert change_count == 1 assert len(list(coin_store.all_unspent_coins())) == 2 ####### # farm a `p2_singleton` pool_reward_puzzle_hash = p2_singleton_puzzle_hash_for_launcher( PUZZLE_DB, SINGLETON_WALLET.launcher_id, SINGLETON_WALLET.launcher_puzzle_hash, DELAY_SECONDS, DELAY_PUZZLE_HASH) farmed_coin = coin_store.farm_coin(pool_reward_puzzle_hash, now) now.seconds += 500 now.height += 1 p2_singleton_coins = filter_p2_singleton(PUZZLE_DB, SINGLETON_WALLET, [farmed_coin]) assert p2_singleton_coins == [farmed_coin] assert len(list(coin_store.all_unspent_coins())) == 3 ####### # now collect the `p2_singleton` for the pool for coin in p2_singleton_coins: p2_singleton_coin_spend, singleton_conditions = claim_p2_singleton( PUZZLE_DB, SINGLETON_WALLET, coin) coin_spend = SINGLETON_WALLET.coin_spend_for_conditions( PUZZLE_DB, pool_leaving_spend_type="claim-p2-nft", pool_reward_amount=p2_singleton_coin_spend.coin.amount, pool_reward_height=now.height - 1, ) spend_bundle = SpendBundle([coin_spend, p2_singleton_coin_spend], G2Element()) additions, removals = coin_store.update_coin_store_for_spend_bundle( spend_bundle, now, MAX_BLOCK_COST_CLVM) now.seconds += 500 now.height += 1 SINGLETON_WALLET.update_state(PUZZLE_DB, removals) assert len(list(coin_store.all_unspent_coins())) == 3 ####### # now finish leaving the pool initial_singleton_puzzle = adaptor_for_singleton_inner_puzzle( ANYONE_CAN_SPEND_PUZZLE) coin_spend = SINGLETON_WALLET.coin_spend_for_conditions( PUZZLE_DB, pool_leaving_spend_type="exit-waiting-room", key_value_list=[("foo1", "bar2"), ("foo2", "baz5")], destination_puzzle_hash=initial_singleton_puzzle.get_tree_hash(), ) spend_bundle = SpendBundle([coin_spend], G2Element()) full_puzzle = singleton_puzzle(SINGLETON_WALLET.launcher_id, SINGLETON_WALLET.launcher_puzzle_hash, initial_singleton_puzzle) PUZZLE_DB.add_puzzle(full_puzzle) try: additions, removals = coin_store.update_coin_store_for_spend_bundle( spend_bundle, now, MAX_BLOCK_COST_CLVM) assert 0 except BadSpendBundleError as ex: assert ex.args[ 0] == "condition validation failure Err.ASSERT_HEIGHT_RELATIVE_FAILED" now.seconds += 350000 now.height += 1445 additions, removals = coin_store.update_coin_store_for_spend_bundle( spend_bundle, now, MAX_BLOCK_COST_CLVM) SINGLETON_WALLET.update_state(PUZZLE_DB, removals) assert len(list(coin_store.all_unspent_coins())) == 3 ####### # now spend to oblivion with the `-113` hack coin_spend = SINGLETON_WALLET.coin_spend_for_conditions( PUZZLE_DB, conditions=[[ConditionOpcode.CREATE_COIN, 0, -113]]) spend_bundle = SpendBundle([coin_spend], G2Element()) spend_bundle.debug() additions, removals = coin_store.update_coin_store_for_spend_bundle( spend_bundle, now, MAX_BLOCK_COST_CLVM) update_count = SINGLETON_WALLET.update_state(PUZZLE_DB, removals) assert update_count == 0 assert len(list(coin_store.all_unspent_coins())) == 2 return 0
def verify(message: str, public_key: str, signature: str): messageBytes = bytes(message, "utf-8") public_key = G1Element.from_bytes(bytes.fromhex(public_key)) signature = G2Element.from_bytes(bytes.fromhex(signature)) print(AugSchemeMPL.verify(public_key, messageBytes, signature))
async def add_spendbundle( self, new_spend: SpendBundle, cost_result: CostResult, spend_name: bytes32, validate_signature=True, ) -> Tuple[Optional[uint64], MempoolInclusionStatus, Optional[Err]]: """ Tries to add spendbundle to either self.mempools or to_pool if it's specified. Returns true if it's added in any of pools, Returns error if it fails. """ start_time = time.time() if self.peak is None: return None, MempoolInclusionStatus.FAILED, Err.MEMPOOL_NOT_INITIALIZED npc_list = cost_result.npc_list cost = cost_result.cost log.debug(f"Cost: {cost}") if cost > self.constants.MAX_BLOCK_COST_CLVM: return None, MempoolInclusionStatus.FAILED, Err.BLOCK_COST_EXCEEDS_MAX if cost_result.error is not None: return None, MempoolInclusionStatus.FAILED, Err(cost_result.error) # build removal list removal_names: List[bytes32] = new_spend.removal_names() additions = additions_for_npc(npc_list) additions_dict: Dict[bytes32, Coin] = {} for add in additions: additions_dict[add.name()] = add addition_amount = uint64(0) # Check additions for max coin amount for coin in additions: if coin.amount >= self.constants.MAX_COIN_AMOUNT: return ( None, MempoolInclusionStatus.FAILED, Err.COIN_AMOUNT_EXCEEDS_MAXIMUM, ) addition_amount = uint64(addition_amount + coin.amount) # Check for duplicate outputs addition_counter = collections.Counter(_.name() for _ in additions) for k, v in addition_counter.items(): if v > 1: return None, MempoolInclusionStatus.FAILED, Err.DUPLICATE_OUTPUT # Check for duplicate inputs removal_counter = collections.Counter(name for name in removal_names) for k, v in removal_counter.items(): if v > 1: return None, MempoolInclusionStatus.FAILED, Err.DOUBLE_SPEND # Skip if already added if spend_name in self.mempool.spends: return uint64(cost), MempoolInclusionStatus.SUCCESS, None removal_record_dict: Dict[bytes32, CoinRecord] = {} removal_coin_dict: Dict[bytes32, Coin] = {} unknown_unspent_error: bool = False removal_amount = uint64(0) for name in removal_names: removal_record = await self.coin_store.get_coin_record(name) if removal_record is None and name not in additions_dict: unknown_unspent_error = True break elif name in additions_dict: removal_coin = additions_dict[name] # TODO(straya): what timestamp to use here? removal_record = CoinRecord( removal_coin, uint32( self.peak.height + 1), # In mempool, so will be included in next height uint32(0), False, False, uint64(int(time.time())), ) assert removal_record is not None removal_amount = uint64(removal_amount + removal_record.coin.amount) removal_record_dict[name] = removal_record removal_coin_dict[name] = removal_record.coin if unknown_unspent_error: return None, MempoolInclusionStatus.FAILED, Err.UNKNOWN_UNSPENT if addition_amount > removal_amount: print(addition_amount, removal_amount) return None, MempoolInclusionStatus.FAILED, Err.MINTING_COIN fees = removal_amount - addition_amount assert_fee_sum: uint64 = uint64(0) for npc in npc_list: if ConditionOpcode.ASSERT_FEE in npc.condition_dict: fee_list: List[ConditionVarPair] = npc.condition_dict[ ConditionOpcode.ASSERT_FEE] for cvp in fee_list: fee = int_from_bytes(cvp.vars[0]) assert_fee_sum = assert_fee_sum + fee if fees < assert_fee_sum: return ( None, MempoolInclusionStatus.FAILED, Err.ASSERT_FEE_CONDITION_FAILED, ) if cost == 0: return None, MempoolInclusionStatus.FAILED, Err.UNKNOWN fees_per_cost: float = fees / cost # If pool is at capacity check the fee, if not then accept even without the fee if self.mempool.at_full_capacity(): if fees == 0: return None, MempoolInclusionStatus.FAILED, Err.INVALID_FEE_LOW_FEE if fees_per_cost < self.mempool.get_min_fee_rate(): return None, MempoolInclusionStatus.FAILED, Err.INVALID_FEE_LOW_FEE # Check removals against UnspentDB + DiffStore + Mempool + SpendBundle # Use this information later when constructing a block fail_reason, conflicts = await self.check_removals(removal_record_dict) # If there is a mempool conflict check if this spendbundle has a higher fee per cost than all others tmp_error: Optional[Err] = None conflicting_pool_items: Dict[bytes32, MempoolItem] = {} if fail_reason is Err.MEMPOOL_CONFLICT: for conflicting in conflicts: sb: MempoolItem = self.mempool.removals[conflicting.name()] conflicting_pool_items[sb.name] = sb for item in conflicting_pool_items.values(): if item.fee_per_cost >= fees_per_cost: self.add_to_potential_tx_set(new_spend, spend_name, cost_result) return ( uint64(cost), MempoolInclusionStatus.PENDING, Err.MEMPOOL_CONFLICT, ) elif fail_reason: return None, MempoolInclusionStatus.FAILED, fail_reason if tmp_error: return None, MempoolInclusionStatus.FAILED, tmp_error # Verify conditions, create hash_key list for aggsig check pks: List[G1Element] = [] msgs: List[bytes32] = [] error: Optional[Err] = None for npc in npc_list: coin_record: CoinRecord = removal_record_dict[npc.coin_name] # Check that the revealed removal puzzles actually match the puzzle hash if npc.puzzle_hash != coin_record.coin.puzzle_hash: log.warning( "Mempool rejecting transaction because of wrong puzzle_hash" ) log.warning( f"{npc.puzzle_hash} != {coin_record.coin.puzzle_hash}") return None, MempoolInclusionStatus.FAILED, Err.WRONG_PUZZLE_HASH chialisp_height = self.peak.prev_transaction_block_height if not self.peak.is_block else self.peak.height error = mempool_check_conditions_dict(coin_record, new_spend, npc.condition_dict, uint32(chialisp_height)) if error: if error is Err.ASSERT_BLOCK_INDEX_EXCEEDS_FAILED or error is Err.ASSERT_BLOCK_AGE_EXCEEDS_FAILED: self.add_to_potential_tx_set(new_spend, spend_name, cost_result) return uint64(cost), MempoolInclusionStatus.PENDING, error break if validate_signature: for pk, message in pkm_pairs_for_conditions_dict( npc.condition_dict, npc.coin_name): pks.append(pk) msgs.append(message) if error: return None, MempoolInclusionStatus.FAILED, error if validate_signature: # Verify aggregated signature if len(pks) == 0 and len(msgs) == 0: validates = new_spend.aggregated_signature == G2Element.infinity( ) else: validates = AugSchemeMPL.aggregate_verify( pks, msgs, new_spend.aggregated_signature) if not validates: log.warning( f"Aggsig validation error {pks} {msgs} {new_spend}") return None, MempoolInclusionStatus.FAILED, Err.BAD_AGGREGATE_SIGNATURE # Remove all conflicting Coins and SpendBundles if fail_reason: mempool_item: MempoolItem for mempool_item in conflicting_pool_items.values(): self.mempool.remove_spend(mempool_item) new_item = MempoolItem(new_spend, fees_per_cost, uint64(fees), cost_result, spend_name) self.mempool.add_to_pool(new_item, additions, removal_coin_dict) log.info(f"add_spendbundle took {time.time() - start_time} seconds") return uint64(cost), MempoolInclusionStatus.SUCCESS, None
async def respond_signatures(self, response: harvester_protocol.RespondSignatures): """ There are two cases: receiving signatures for sps, or receiving signatures for the block. """ if response.sp_hash not in self.farmer.sps: self.farmer.log.warning(f"Do not have challenge hash {response.challenge_hash}") return None is_sp_signatures: bool = False sps = self.farmer.sps[response.sp_hash] signage_point_index = sps[0].signage_point_index found_sp_hash_debug = False for sp_candidate in sps: if response.sp_hash == response.message_signatures[0][0]: found_sp_hash_debug = True if sp_candidate.reward_chain_sp == response.message_signatures[1][0]: is_sp_signatures = True if found_sp_hash_debug: assert is_sp_signatures pospace = None for plot_identifier, candidate_pospace in self.farmer.proofs_of_space[response.sp_hash]: if plot_identifier == response.plot_identifier: pospace = candidate_pospace assert pospace is not None include_taproot: bool = pospace.pool_contract_puzzle_hash is not None computed_quality_string = pospace.verify_and_get_quality_string( self.farmer.constants, response.challenge_hash, response.sp_hash ) if computed_quality_string is None: self.farmer.log.warning(f"Have invalid PoSpace {pospace}") return None if is_sp_signatures: ( challenge_chain_sp, challenge_chain_sp_harv_sig, ) = response.message_signatures[0] reward_chain_sp, reward_chain_sp_harv_sig = response.message_signatures[1] for sk in self.farmer.get_private_keys(): pk = sk.get_g1() if pk == response.farmer_pk: agg_pk = ProofOfSpace.generate_plot_public_key(response.local_pk, pk, include_taproot) assert agg_pk == pospace.plot_public_key if include_taproot: taproot_sk: PrivateKey = ProofOfSpace.generate_taproot_sk(response.local_pk, pk) taproot_share_cc_sp: G2Element = AugSchemeMPL.sign(taproot_sk, challenge_chain_sp, agg_pk) taproot_share_rc_sp: G2Element = AugSchemeMPL.sign(taproot_sk, reward_chain_sp, agg_pk) else: taproot_share_cc_sp = G2Element() taproot_share_rc_sp = G2Element() farmer_share_cc_sp = AugSchemeMPL.sign(sk, challenge_chain_sp, agg_pk) agg_sig_cc_sp = AugSchemeMPL.aggregate( [challenge_chain_sp_harv_sig, farmer_share_cc_sp, taproot_share_cc_sp] ) assert AugSchemeMPL.verify(agg_pk, challenge_chain_sp, agg_sig_cc_sp) # This means it passes the sp filter farmer_share_rc_sp = AugSchemeMPL.sign(sk, reward_chain_sp, agg_pk) agg_sig_rc_sp = AugSchemeMPL.aggregate( [reward_chain_sp_harv_sig, farmer_share_rc_sp, taproot_share_rc_sp] ) assert AugSchemeMPL.verify(agg_pk, reward_chain_sp, agg_sig_rc_sp) if pospace.pool_public_key is not None: assert pospace.pool_contract_puzzle_hash is None pool_pk = bytes(pospace.pool_public_key) if pool_pk not in self.farmer.pool_sks_map: self.farmer.log.error( f"Don't have the private key for the pool key used by harvester: {pool_pk.hex()}" ) return None pool_target: Optional[PoolTarget] = PoolTarget(self.farmer.pool_target, uint32(0)) assert pool_target is not None pool_target_signature: Optional[G2Element] = AugSchemeMPL.sign( self.farmer.pool_sks_map[pool_pk], bytes(pool_target) ) else: assert pospace.pool_contract_puzzle_hash is not None pool_target = None pool_target_signature = None request = farmer_protocol.DeclareProofOfSpace( response.challenge_hash, challenge_chain_sp, signage_point_index, reward_chain_sp, pospace, agg_sig_cc_sp, agg_sig_rc_sp, self.farmer.farmer_target, pool_target, pool_target_signature, ) self.farmer.state_changed("proof", {"proof": request, "passed_filter": True}) msg = make_msg(ProtocolMessageTypes.declare_proof_of_space, request) await self.farmer.server.send_to_all([msg], NodeType.FULL_NODE) return None else: # This is a response with block signatures for sk in self.farmer.get_private_keys(): ( foliage_block_data_hash, foliage_sig_harvester, ) = response.message_signatures[0] ( foliage_transaction_block_hash, foliage_transaction_block_sig_harvester, ) = response.message_signatures[1] pk = sk.get_g1() if pk == response.farmer_pk: agg_pk = ProofOfSpace.generate_plot_public_key(response.local_pk, pk, include_taproot) assert agg_pk == pospace.plot_public_key if include_taproot: taproot_sk = ProofOfSpace.generate_taproot_sk(response.local_pk, pk) foliage_sig_taproot: G2Element = AugSchemeMPL.sign(taproot_sk, foliage_block_data_hash, agg_pk) foliage_transaction_block_sig_taproot: G2Element = AugSchemeMPL.sign( taproot_sk, foliage_transaction_block_hash, agg_pk ) else: foliage_sig_taproot = G2Element() foliage_transaction_block_sig_taproot = G2Element() foliage_sig_farmer = AugSchemeMPL.sign(sk, foliage_block_data_hash, agg_pk) foliage_transaction_block_sig_farmer = AugSchemeMPL.sign(sk, foliage_transaction_block_hash, agg_pk) foliage_agg_sig = AugSchemeMPL.aggregate( [foliage_sig_harvester, foliage_sig_farmer, foliage_sig_taproot] ) foliage_block_agg_sig = AugSchemeMPL.aggregate( [ foliage_transaction_block_sig_harvester, foliage_transaction_block_sig_farmer, foliage_transaction_block_sig_taproot, ] ) assert AugSchemeMPL.verify(agg_pk, foliage_block_data_hash, foliage_agg_sig) assert AugSchemeMPL.verify(agg_pk, foliage_transaction_block_hash, foliage_block_agg_sig) request_to_nodes = farmer_protocol.SignedValues( computed_quality_string, foliage_agg_sig, foliage_block_agg_sig, ) msg = make_msg(ProtocolMessageTypes.signed_values, request_to_nodes) await self.farmer.server.send_to_all([msg], NodeType.FULL_NODE)
def test_elements(): b1 = BNWrapper([1, 2]) b2 = BNWrapper([3, 1, 4, 1, 5, 9]) i1 = int.from_bytes(bytes([1, 2]), byteorder="big") i2 = int.from_bytes(bytes([3, 1, 4, 1, 5, 9]), byteorder="big") g1 = G1Element.generator() g2 = G2Element.generator() u1 = G1Element.infinity() # unity u2 = G2Element.infinity() # Does not allow construction try: i = G1Element() assert False except Exception: pass try: i = G2Element() assert False except Exception: pass x1 = g1 * b1 x2 = g1 * b2 y1 = g2 * b1 y2 = g2 * b2 # Implicit conversion from python ints to BNWrapperWrapperWrapper assert x1 == g1 * i1 == i1 * g1 assert x2 == g1 * i2 == i2 * g1 assert y1 == g2 * i1 == i1 * g2 assert y2 == g2 * i2 == i2 * g2 # G1 assert x1 != x2 assert x1 * b1 == b1 * x1 assert x1 * b1 != x1 * b2 assert x1 + u1 == x1 assert x1 + x2 == x2 + x1 assert x1 + x1.negate() == u1 assert x1 == G1Element(bytes(x1)) copy = deepcopy(x1) assert x1 == copy x1 += x2 assert x1 != copy # G2 assert y1 != y2 assert y1 * b1 == b1 * y1 assert y1 * b1 != y1 * b2 assert y1 + u2 == y1 assert y1 + y2 == y2 + y1 assert y1 + y1.negate() == u2 assert y1 == G2Element(bytes(y1)) copy = deepcopy(y1) assert y1 == copy y1 += y2 assert y1 != copy # pairing operation pair = x1 & y1 assert pair != x1 & y2 assert pair != x2 & y1 assert pair == x1.pair(y1) assert pair == GTElement(bytes(pair)) copy = deepcopy(pair) assert pair == copy pair = None assert pair != copy