chia-blockchain/tests/clvm/test_singletons.py
Matt Hauff e5b94d34a0
Chialisp file extensions (#14996)
* Rename chialisp file extensions

* Add pre-commit check for .clvm files

* Delete sha256tree files

* remove hash files in favor of central dictionary

* Add check for missing files like hex and hash entries

* Enhance clsp pre-commit check

* Actually check hash matches

* Update tools/manage_clvm.py

Co-authored-by: Kyle Altendorf <sda@fstab.net>

* Update tools/manage_clvm.py

Co-authored-by: Kyle Altendorf <sda@fstab.net>

* Fix Windows file writing

* Fix setup.py package_data fields

* Load hash dict at runtime

* Move away from exception pattern

* Bad equality check

* Minor fixes

* remove trailing whitespace fix

---------

Co-authored-by: Kyle Altendorf <sda@fstab.net>
2023-04-18 11:27:17 -05:00

544 lines
21 KiB
Python

from __future__ import annotations
from typing import List, Optional, Tuple
import pytest
from blspy import AugSchemeMPL, G1Element, G2Element, PrivateKey
from chia.clvm.spend_sim import CostLogger, SimClient, SpendSim, sim_and_client
from chia.consensus.default_constants import DEFAULT_CONSTANTS
from chia.types.blockchain_format.coin import Coin
from chia.types.blockchain_format.program import Program
from chia.types.blockchain_format.sized_bytes import bytes32
from chia.types.coin_spend import CoinSpend
from chia.types.condition_opcodes import ConditionOpcode
from chia.types.spend_bundle import SpendBundle
from chia.util.errors import Err
from chia.util.ints import uint64
from chia.wallet.lineage_proof import LineageProof
from chia.wallet.puzzles import p2_conditions, p2_delegated_puzzle_or_hidden_puzzle
from tests.clvm.test_puzzles import public_key_for_index, secret_exponent_for_index
from tests.util.key_tool import KeyTool
"""
This test suite aims to test:
- chia.wallet.puzzles.singleton_top_layer.py
- chia.wallet.puzzles.singleton_top_layer.clsp
- chia.wallet.puzzles.singleton_top_layer_v1_1.py
- chia.wallet.puzzles.singleton_top_layer_v1_1.clsp
- chia.wallet.puzzles.p2_singleton.clsp
- chia.wallet.puzzles.p2_singleton_or_delayed_puzhash.clsp
"""
class TransactionPushError(Exception):
pass
class TestSingleton:
# Helper function
def sign_delegated_puz(self, del_puz: Program, coin: Coin) -> G2Element:
synthetic_secret_key: PrivateKey = p2_delegated_puzzle_or_hidden_puzzle.calculate_synthetic_secret_key( # noqa
PrivateKey.from_bytes(
secret_exponent_for_index(1).to_bytes(32, "big"),
),
p2_delegated_puzzle_or_hidden_puzzle.DEFAULT_HIDDEN_PUZZLE_HASH,
)
return AugSchemeMPL.sign(
synthetic_secret_key,
(del_puz.get_tree_hash() + coin.name() + DEFAULT_CONSTANTS.AGG_SIG_ME_ADDITIONAL_DATA), # noqa
)
# Helper function
async def make_and_spend_bundle(
self,
sim: SpendSim,
sim_client: SimClient,
coin: Coin,
delegated_puzzle: Program,
coinsols: List[CoinSpend],
ex_error: Optional[Err] = None,
fail_msg: str = "",
cost_logger: Optional[CostLogger] = None,
cost_log_msg: str = "",
):
signature: G2Element = self.sign_delegated_puz(delegated_puzzle, coin)
spend_bundle = SpendBundle(
coinsols,
signature,
)
if cost_logger is not None:
spend_bundle = cost_logger.add_cost(cost_log_msg, spend_bundle)
try:
result, error = await sim_client.push_tx(spend_bundle)
if error is None:
await sim.farm_block()
elif ex_error is not None:
assert error == ex_error
else:
raise TransactionPushError(error)
except AssertionError:
raise AssertionError(fail_msg)
@pytest.mark.asyncio
@pytest.mark.parametrize("version", [0, 1])
async def test_singleton_top_layer(self, version, cost_logger):
async with sim_and_client() as (sim, sim_client):
# START TESTS
# Generate starting info
key_lookup = KeyTool()
pk: G1Element = G1Element.from_bytes(public_key_for_index(1, key_lookup))
starting_puzzle: Program = p2_delegated_puzzle_or_hidden_puzzle.puzzle_for_pk(pk) # noqa
if version == 0:
from chia.wallet.puzzles import singleton_top_layer
adapted_puzzle: Program = singleton_top_layer.adapt_inner_to_singleton(starting_puzzle) # noqa
else:
from chia.wallet.puzzles import singleton_top_layer_v1_1 as singleton_top_layer
adapted_puzzle = starting_puzzle
adapted_puzzle_hash: bytes32 = adapted_puzzle.get_tree_hash()
# Get our starting standard coin created
START_AMOUNT: uint64 = 1023
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],
cost_logger=cost_logger,
cost_log_msg="Singleton Launch + Standard TX",
)
# 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],
cost_logger=cost_logger,
cost_log_msg="Singleton Eve Spend w/ Standard TX",
)
# 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],
cost_logger=cost_logger,
cost_log_msg="Singleton Spend + Standard TX",
)
# 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],
cost_logger=cost_logger,
cost_log_msg="Singleton w/ Standard TX claim p2_singleton",
)
# 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],
cost_logger=cost_logger,
cost_log_msg="Singleton w/ Standard TX claim p2_singleton_or_delayed",
)
# 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 = next(filter(lambda c: c.amount == 2, (await sim.all_non_reward_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 = 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 if version == 0 else Err.ASSERT_MY_AMOUNT_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],
cost_logger=cost_logger,
cost_log_msg="Singleton w/ Standard TX melt",
)
melted_coin: Coin = (await sim.all_non_reward_coins())[0]
assert melted_coin.puzzle_hash == adapted_puzzle_hash