mirror of
https://github.com/Chia-Network/chia-blockchain.git
synced 2026-08-24 10:05:29 -05:00
Convert PlotNFT drivers
This commit is contained in:
@@ -109,7 +109,7 @@ async def test_singleton_top_layer(cost_logger: CostLogger) -> None:
|
||||
await push_bundle(
|
||||
sim,
|
||||
sim_client,
|
||||
[starting_spend, launch_result.launcher_spend],
|
||||
[starting_spend, *launch_result.necessary_spends],
|
||||
cost_logger=cost_logger,
|
||||
cost_log_msg="Singleton Launch + ACS",
|
||||
)
|
||||
@@ -126,7 +126,7 @@ async def test_singleton_top_layer(cost_logger: CostLogger) -> None:
|
||||
cost_logger=cost_logger,
|
||||
cost_log_msg="Singleton Eve Spend w/ ACS",
|
||||
)
|
||||
singleton.inner_puzzle = ACS
|
||||
singleton = singleton.with_inner_puzzle(ACS)
|
||||
|
||||
await sim.farm_block(ACS_PH) # for non-FF spends
|
||||
|
||||
@@ -140,7 +140,7 @@ async def test_singleton_top_layer(cost_logger: CostLogger) -> None:
|
||||
cost_logger=cost_logger,
|
||||
cost_log_msg="Singleton Spend + ACS",
|
||||
)
|
||||
singleton.inner_puzzle = ACS
|
||||
singleton = singleton.with_inner_puzzle(ACS)
|
||||
|
||||
# CLAIM A P2_SINGLETON
|
||||
assert singleton.coin == await odd_singleton_coin(sim)
|
||||
@@ -171,7 +171,7 @@ async def test_singleton_top_layer(cost_logger: CostLogger) -> None:
|
||||
cost_logger=cost_logger,
|
||||
cost_log_msg="Singleton w/ ACS claim p2_singleton",
|
||||
)
|
||||
singleton.inner_puzzle = ACS
|
||||
singleton = singleton.with_inner_puzzle(ACS)
|
||||
assert len(await sim_client.get_coin_records_by_puzzle_hash(bytes32.zeros, include_spent_coins=False)) == 1
|
||||
|
||||
# CREATE MULTIPLE ODD CHILDREN (Negative Test)
|
||||
|
||||
@@ -13,12 +13,9 @@ from chia._tests.util.spend_sim import CostLogger, SimClient, SpendSim, sim_and_
|
||||
from chia.pools.plotnft_drivers import (
|
||||
GetNextPlotNFTError,
|
||||
PlotNFT,
|
||||
PlotNFTPuzzle,
|
||||
PlotNFTInnerPuzzle,
|
||||
PoolConfig,
|
||||
PoolReward,
|
||||
RewardPuzzle,
|
||||
SingletonPuzzles,
|
||||
SingletonStruct,
|
||||
UserConfig,
|
||||
)
|
||||
from chia.types.blockchain_format.program import Program, run
|
||||
@@ -34,11 +31,13 @@ from chia.wallet.conditions import (
|
||||
SendMessage,
|
||||
parse_conditions_non_consensus,
|
||||
)
|
||||
from chia.wallet.puzzles.custody.custody_architecture import DelegatedPuzzleAndSolution, PuzzleWithRestrictions
|
||||
from chia.wallet.puzzles.custody.custody_architecture import PuzzleWithRestrictions
|
||||
from chia.wallet.puzzles.p2_delegated_puzzle_or_hidden_puzzle import (
|
||||
DEFAULT_HIDDEN_PUZZLE_HASH,
|
||||
calculate_synthetic_secret_key,
|
||||
)
|
||||
from chia.wallet.puzzles.puzzle_drivers import DelegatedPuzzleAndSolution, UnknownPuzzle, UnknownSolution
|
||||
from chia.wallet.puzzles.singleton_drivers import P2SingletonPuzzle, SingletonCorePuzzles, SingletonStruct
|
||||
from chia.wallet.uncurried_puzzle import UncurriedPuzzle
|
||||
from chia.wallet.wallet_spend_bundle import WalletSpendBundle
|
||||
|
||||
@@ -71,7 +70,7 @@ async def mint_plotnft(
|
||||
|
||||
fund_coin, _ = await sim_client.get_coin_records_by_puzzle_hash(ACS_PH, include_spent_coins=False)
|
||||
|
||||
conditions, spends, plotnft = PlotNFT.launch(
|
||||
launch_result = PlotNFT.launch_plotnft(
|
||||
origin_coins=[fund_coin.coin],
|
||||
user_config=UserConfig(synthetic_pubkey=user_sk.get_g1()),
|
||||
pool_config=None
|
||||
@@ -81,23 +80,16 @@ async def mint_plotnft(
|
||||
hint=bytes32.zeros,
|
||||
exiting=desired_state == "waiting_room",
|
||||
)
|
||||
plotnft = launch_result.launched_singleton
|
||||
|
||||
result = await sim_client.push_tx(
|
||||
WalletSpendBundle(
|
||||
[
|
||||
*spends,
|
||||
*launch_result.necessary_spends,
|
||||
make_spend(
|
||||
coin=fund_coin.coin,
|
||||
puzzle_reveal=ACS,
|
||||
solution=Program.to(
|
||||
[condition.to_program() for condition in conditions]
|
||||
+ [
|
||||
CreateCoin(
|
||||
puzzle_hash=plotnft.singleton_struct.singleton_puzzles.singleton_launcher_hash,
|
||||
amount=uint64(1),
|
||||
).to_program()
|
||||
]
|
||||
),
|
||||
solution=Program.to([condition.to_program() for condition in launch_result.necessary_conditions]),
|
||||
),
|
||||
],
|
||||
G2Element(),
|
||||
@@ -108,7 +100,9 @@ async def mint_plotnft(
|
||||
|
||||
# Test syncing from launcher
|
||||
assert (
|
||||
PlotNFT.get_next_from_coin_spend(coin_spend=spends[1], genesis_challenge=sim.defaults.GENESIS_CHALLENGE)
|
||||
PlotNFT.get_next_from_coin_spend(
|
||||
coin_spend=launch_result.necessary_spends[1], genesis_challenge=sim.defaults.GENESIS_CHALLENGE
|
||||
)
|
||||
== plotnft
|
||||
)
|
||||
return plotnft
|
||||
@@ -123,14 +117,16 @@ async def test_plotnft_transitions(cost_logger: CostLogger) -> None:
|
||||
with pytest.raises(ValueError, match=re.escape("Cannot exit to waiting room while self pooling.")):
|
||||
plotnft.exit_to_waiting_room(
|
||||
delegated_puzzle_and_solution=DelegatedPuzzleAndSolution(
|
||||
puzzle=Program.to(None), solution=Program.to(None)
|
||||
puzzle=UnknownPuzzle(known_puzzle=Program.to(None)),
|
||||
solution=UnknownSolution(solution=Program.to(None)),
|
||||
)
|
||||
)
|
||||
|
||||
with pytest.raises(ValueError, match=re.escape("Cannot exit waiting room while self pooling.")):
|
||||
plotnft.exit_waiting_room(
|
||||
delegated_puzzle_and_solution=DelegatedPuzzleAndSolution(
|
||||
puzzle=Program.to(None), solution=Program.to(None)
|
||||
puzzle=UnknownPuzzle(known_puzzle=Program.to(None)),
|
||||
solution=UnknownSolution(solution=Program.to(None)),
|
||||
)
|
||||
)
|
||||
|
||||
@@ -138,7 +134,7 @@ async def test_plotnft_transitions(cost_logger: CostLogger) -> None:
|
||||
fee_hook = CreateCoinAnnouncement(msg=b"", coin_id=plotnft.coin.name())
|
||||
url_remark = Remark(rest=Program.to("url"))
|
||||
coin_spends = plotnft.join_pool(
|
||||
user_config=plotnft.user_config,
|
||||
user_config=plotnft.inner_puzzle.user_config,
|
||||
pool_config=PoolConfig(
|
||||
pool_puzzle_hash=POOL_PUZZLE_HASH, heightlock=uint32(5), pool_memoization=Program.to(["pool"])
|
||||
),
|
||||
@@ -167,21 +163,23 @@ async def test_plotnft_transitions(cost_logger: CostLogger) -> None:
|
||||
with pytest.raises(ValueError, match=re.escape("Cannot exit waiting room while not in it")):
|
||||
plotnft.exit_waiting_room(
|
||||
delegated_puzzle_and_solution=DelegatedPuzzleAndSolution(
|
||||
puzzle=Program.to(None), solution=Program.to(None)
|
||||
puzzle=UnknownPuzzle(known_puzzle=Program.to(None)),
|
||||
solution=UnknownSolution(solution=Program.to(None)),
|
||||
)
|
||||
)
|
||||
|
||||
# Attempt to leave without waiting room
|
||||
quick_exit_dpuz_and_solution = DelegatedPuzzleAndSolution(
|
||||
puzzle=ACS, solution=Program.to([CreateCoin(bytes32.zeros, uint64(1)).to_program()])
|
||||
puzzle=UnknownPuzzle(known_puzzle=ACS),
|
||||
solution=UnknownSolution(solution=Program.to([CreateCoin(bytes32.zeros, uint64(1)).to_program()])),
|
||||
)
|
||||
singing_info = plotnft.modify_delegated_puzzle_and_solution(quick_exit_dpuz_and_solution)
|
||||
singing_info = plotnft.inner_puzzle.modify_delegated_puzzle_and_solution(quick_exit_dpuz_and_solution)
|
||||
coin_spends = plotnft.exit_to_waiting_room(quick_exit_dpuz_and_solution)
|
||||
result = await sim_client.push_tx(
|
||||
WalletSpendBundle(
|
||||
coin_spends,
|
||||
user_sk.sign(
|
||||
singing_info.puzzle.get_tree_hash() + plotnft.coin.name() + sim.defaults.AGG_SIG_ME_ADDITIONAL_DATA
|
||||
singing_info.puzzle.puzzle_hash + plotnft.coin.name() + sim.defaults.AGG_SIG_ME_ADDITIONAL_DATA
|
||||
),
|
||||
)
|
||||
)
|
||||
@@ -189,25 +187,27 @@ async def test_plotnft_transitions(cost_logger: CostLogger) -> None:
|
||||
|
||||
# # Attempt to make a message while leaving
|
||||
message_dpuz_and_solution = DelegatedPuzzleAndSolution(
|
||||
puzzle=ACS,
|
||||
solution=Program.to(
|
||||
[
|
||||
plotnft.exit_to_waiting_room_condition().to_program(),
|
||||
SendMessage(
|
||||
bytes32.zeros,
|
||||
sender=MessageParticipant(parent_id_committed=bytes32.zeros),
|
||||
receiver=MessageParticipant(parent_id_committed=bytes32.zeros),
|
||||
).to_program(),
|
||||
]
|
||||
puzzle=UnknownPuzzle(known_puzzle=ACS),
|
||||
solution=UnknownSolution(
|
||||
solution=Program.to(
|
||||
[
|
||||
plotnft.inner_puzzle.exit_to_waiting_room_condition.to_program(),
|
||||
SendMessage(
|
||||
bytes32.zeros,
|
||||
sender=MessageParticipant(parent_id_committed=bytes32.zeros),
|
||||
receiver=MessageParticipant(parent_id_committed=bytes32.zeros),
|
||||
).to_program(),
|
||||
]
|
||||
)
|
||||
),
|
||||
)
|
||||
singing_info = plotnft.modify_delegated_puzzle_and_solution(message_dpuz_and_solution)
|
||||
singing_info = plotnft.inner_puzzle.modify_delegated_puzzle_and_solution(message_dpuz_and_solution)
|
||||
coin_spends = plotnft.exit_to_waiting_room(message_dpuz_and_solution)
|
||||
result = await sim_client.push_tx(
|
||||
WalletSpendBundle(
|
||||
coin_spends,
|
||||
user_sk.sign(
|
||||
singing_info.puzzle.get_tree_hash() + plotnft.coin.name() + sim.defaults.AGG_SIG_ME_ADDITIONAL_DATA
|
||||
singing_info.puzzle.puzzle_hash + plotnft.coin.name() + sim.defaults.AGG_SIG_ME_ADDITIONAL_DATA
|
||||
),
|
||||
)
|
||||
)
|
||||
@@ -215,10 +215,12 @@ async def test_plotnft_transitions(cost_logger: CostLogger) -> None:
|
||||
|
||||
# Leave honestly
|
||||
honest_exit_dpuz_and_solution = DelegatedPuzzleAndSolution(
|
||||
puzzle=ACS,
|
||||
solution=Program.to([plotnft.exit_to_waiting_room_condition().to_program()]),
|
||||
puzzle=UnknownPuzzle(known_puzzle=ACS),
|
||||
solution=UnknownSolution(
|
||||
solution=Program.to([plotnft.inner_puzzle.exit_to_waiting_room_condition.to_program()])
|
||||
),
|
||||
)
|
||||
singing_info = plotnft.modify_delegated_puzzle_and_solution(honest_exit_dpuz_and_solution)
|
||||
singing_info = plotnft.inner_puzzle.modify_delegated_puzzle_and_solution(honest_exit_dpuz_and_solution)
|
||||
coin_spends = plotnft.exit_to_waiting_room(honest_exit_dpuz_and_solution)
|
||||
result = await sim_client.push_tx(
|
||||
cost_logger.add_cost(
|
||||
@@ -226,54 +228,61 @@ async def test_plotnft_transitions(cost_logger: CostLogger) -> None:
|
||||
WalletSpendBundle(
|
||||
coin_spends,
|
||||
user_sk.sign(
|
||||
singing_info.puzzle.get_tree_hash()
|
||||
+ plotnft.coin.name()
|
||||
+ sim.defaults.AGG_SIG_ME_ADDITIONAL_DATA
|
||||
singing_info.puzzle.puzzle_hash + plotnft.coin.name() + sim.defaults.AGG_SIG_ME_ADDITIONAL_DATA
|
||||
),
|
||||
),
|
||||
)
|
||||
)
|
||||
assert result == (MempoolInclusionStatus.SUCCESS, None)
|
||||
await sim.farm_block()
|
||||
plotnft = PlotNFT.get_next_from_coin_spend(coin_spend=coin_spends[0], previous_plotnft_puzzle=plotnft)
|
||||
plotnft = PlotNFT.get_next_from_coin_spend(
|
||||
coin_spend=coin_spends[0], previous_plotnft_puzzle=plotnft.inner_puzzle
|
||||
)
|
||||
|
||||
with pytest.raises(ValueError, match=re.escape("Already exiting to waiting room, cannot exit again")):
|
||||
plotnft.exit_to_waiting_room(
|
||||
delegated_puzzle_and_solution=DelegatedPuzzleAndSolution(
|
||||
puzzle=Program.to(None), solution=Program.to(None)
|
||||
puzzle=UnknownPuzzle(known_puzzle=Program.to(None)),
|
||||
solution=UnknownSolution(solution=Program.to(None)),
|
||||
)
|
||||
)
|
||||
|
||||
# Return to self-pooling
|
||||
exit_dpuz_and_solution = DelegatedPuzzleAndSolution(
|
||||
puzzle=ACS,
|
||||
solution=Program.to([cond.to_program() for cond in plotnft.exit_from_waiting_room_conditions()]),
|
||||
puzzle=UnknownPuzzle(known_puzzle=ACS),
|
||||
solution=UnknownSolution(
|
||||
solution=Program.to(
|
||||
[cond.to_program() for cond in plotnft.inner_puzzle.exit_from_waiting_room_conditions]
|
||||
)
|
||||
),
|
||||
)
|
||||
singing_info = plotnft.modify_delegated_puzzle_and_solution(exit_dpuz_and_solution)
|
||||
singing_info = plotnft.inner_puzzle.modify_delegated_puzzle_and_solution(exit_dpuz_and_solution)
|
||||
coin_spends = plotnft.exit_waiting_room(exit_dpuz_and_solution)
|
||||
timelocked_spend = WalletSpendBundle(
|
||||
coin_spends,
|
||||
user_sk.sign(
|
||||
singing_info.puzzle.get_tree_hash() + plotnft.coin.name() + sim.defaults.AGG_SIG_ME_ADDITIONAL_DATA
|
||||
singing_info.puzzle.puzzle_hash + plotnft.coin.name() + sim.defaults.AGG_SIG_ME_ADDITIONAL_DATA
|
||||
),
|
||||
)
|
||||
result = await sim_client.push_tx(timelocked_spend)
|
||||
assert result == (MempoolInclusionStatus.PENDING, Err.ASSERT_HEIGHT_RELATIVE_FAILED)
|
||||
for _ in range(plotnft.guaranteed_pool_config.heightlock):
|
||||
for _ in range(plotnft.inner_puzzle.guaranteed_pool_config.heightlock):
|
||||
await sim.farm_block()
|
||||
result = await sim_client.push_tx(cost_logger.add_cost("Waiting Room -> Self Custody", timelocked_spend))
|
||||
assert result == (MempoolInclusionStatus.SUCCESS, None)
|
||||
await sim.farm_block()
|
||||
|
||||
# Check that it's there
|
||||
plotnft = PlotNFT.get_next_from_coin_spend(coin_spend=coin_spends[0], previous_plotnft_puzzle=plotnft)
|
||||
plotnft = PlotNFT.get_next_from_coin_spend(
|
||||
coin_spend=coin_spends[0], previous_plotnft_puzzle=plotnft.inner_puzzle
|
||||
)
|
||||
assert await sim_client.get_coin_record_by_name(plotnft.coin.name()) is not None
|
||||
|
||||
|
||||
async def mint_reward(sim: SpendSim, sim_client: SimClient, singleton_id: bytes32) -> PoolReward:
|
||||
reward_puzzle = RewardPuzzle(singleton_id=singleton_id)
|
||||
await sim.farm_block(reward_puzzle.puzzle_hash())
|
||||
coin_1, coin_2 = await sim_client.get_coin_records_by_puzzle_hash(reward_puzzle.puzzle_hash())
|
||||
reward_puzzle = P2SingletonPuzzle(singleton_id=singleton_id)
|
||||
await sim.farm_block(reward_puzzle.puzzle_hash)
|
||||
coin_1, coin_2 = await sim_client.get_coin_records_by_puzzle_hash(reward_puzzle.puzzle_hash)
|
||||
return PoolReward(
|
||||
coin=coin_1.coin if coin_1.coin.amount > coin_2.coin.amount else coin_2.coin,
|
||||
singleton_id=singleton_id,
|
||||
@@ -299,7 +308,8 @@ async def test_plotnft_self_custody_claim(cost_logger: CostLogger) -> None:
|
||||
plotnft.claim_pool_rewards(rewards_to_claim=[reward], reward_delegated_puzzles_and_solutions=[])
|
||||
|
||||
reward_dpuz_and_sol = DelegatedPuzzleAndSolution(
|
||||
puzzle=ACS, solution=Program.to([CreateCoin(bytes32.zeros, uint64(1)).to_program()])
|
||||
puzzle=UnknownPuzzle(known_puzzle=ACS),
|
||||
solution=UnknownSolution(solution=Program.to([CreateCoin(bytes32.zeros, uint64(1)).to_program()])),
|
||||
)
|
||||
coin_spends = plotnft.claim_pool_rewards(
|
||||
rewards_to_claim=[reward], reward_delegated_puzzles_and_solutions=[reward_dpuz_and_sol]
|
||||
@@ -320,7 +330,9 @@ async def test_plotnft_self_custody_claim(cost_logger: CostLogger) -> None:
|
||||
assert len(await sim_client.get_coin_records_by_puzzle_hash(bytes32.zeros)) == 1
|
||||
|
||||
# Make sure we can find the plotnft
|
||||
plotnft = PlotNFT.get_next_from_coin_spend(coin_spend=coin_spends[0], previous_plotnft_puzzle=plotnft)
|
||||
plotnft = PlotNFT.get_next_from_coin_spend(
|
||||
coin_spend=coin_spends[0], previous_plotnft_puzzle=plotnft.inner_puzzle
|
||||
)
|
||||
|
||||
|
||||
# PlotNFT claims pooling rewards while pooling
|
||||
@@ -340,7 +352,10 @@ async def test_plotnft_pooling_claim(
|
||||
plotnft.claim_pool_rewards(
|
||||
rewards_to_claim=[reward],
|
||||
reward_delegated_puzzles_and_solutions=[
|
||||
DelegatedPuzzleAndSolution(puzzle=Program.to(None), solution=Program.to(None))
|
||||
DelegatedPuzzleAndSolution(
|
||||
puzzle=UnknownPuzzle(known_puzzle=Program.to(None)),
|
||||
solution=UnknownSolution(solution=Program.to(None)),
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
@@ -359,27 +374,34 @@ async def test_plotnft_pooling_claim(
|
||||
|
||||
# Make sure the pooling reward did what it was supposed to
|
||||
assert (
|
||||
len(await sim_client.get_coin_records_by_puzzle_hash(plotnft.guaranteed_pool_config.pool_puzzle_hash)) == 1
|
||||
len(
|
||||
await sim_client.get_coin_records_by_puzzle_hash(
|
||||
plotnft.inner_puzzle.guaranteed_pool_config.pool_puzzle_hash
|
||||
)
|
||||
)
|
||||
== 1
|
||||
)
|
||||
|
||||
# Make sure we can find the plotnft
|
||||
plotnft = PlotNFT.get_next_from_coin_spend(coin_spend=coin_spends[0], previous_plotnft_puzzle=plotnft)
|
||||
plotnft = PlotNFT.get_next_from_coin_spend(
|
||||
coin_spend=coin_spends[0], previous_plotnft_puzzle=plotnft.inner_puzzle
|
||||
)
|
||||
|
||||
|
||||
def test_plotnft_errors() -> None:
|
||||
with pytest.raises(
|
||||
ValueError, match=re.escape("Cannot initialize a PlotNFTPuzzle with an empty pool config and exiting=True")
|
||||
):
|
||||
PlotNFTPuzzle(
|
||||
launcher_id=bytes32.zeros,
|
||||
PlotNFTInnerPuzzle(
|
||||
self_launcher_id=bytes32.zeros,
|
||||
genesis_challenge=bytes32.zeros,
|
||||
user_config=UserConfig(synthetic_pubkey=user_sk.get_g1()),
|
||||
exiting=True,
|
||||
)
|
||||
|
||||
with pytest.raises(ValueError, match=re.escape("Plot NFT is not pooling, cannot retrieve pool config")):
|
||||
PlotNFTPuzzle(
|
||||
launcher_id=bytes32.zeros,
|
||||
PlotNFTInnerPuzzle(
|
||||
self_launcher_id=bytes32.zeros,
|
||||
genesis_challenge=bytes32.zeros,
|
||||
user_config=UserConfig(synthetic_pubkey=user_sk.get_g1()),
|
||||
exiting=False,
|
||||
@@ -404,10 +426,10 @@ def test_plotnft_errors() -> None:
|
||||
Program.to(PlotNFT.singleton_puzzles.singleton_mod).curry(
|
||||
SingletonStruct(
|
||||
launcher_id=bytes32.zeros,
|
||||
singleton_puzzles=SingletonPuzzles(
|
||||
singleton_puzzles=SingletonCorePuzzles(
|
||||
singleton_launcher=Program.to("not the launcher"), singleton_launcher_hash_pre_computed=None
|
||||
),
|
||||
).to_program()
|
||||
).program
|
||||
),
|
||||
Program.to(None),
|
||||
),
|
||||
@@ -417,7 +439,7 @@ def test_plotnft_errors() -> None:
|
||||
def wrap_inner_puz(inner_puz: Program) -> UncurriedPuzzle:
|
||||
return UncurriedPuzzle(
|
||||
mod=PlotNFT.singleton_puzzles.singleton_mod,
|
||||
args=Program.to([SingletonStruct(launcher_id=bytes32.zeros).to_program(), inner_puz]),
|
||||
args=Program.to([SingletonStruct(launcher_id=bytes32.zeros).program, inner_puz]),
|
||||
)
|
||||
|
||||
FAUX_SPEND = make_spend(default_coin, Program.to(None), Program.to([None, None, None]))
|
||||
@@ -538,10 +560,10 @@ def test_plotnft_errors() -> None:
|
||||
|
||||
def test_singleton_constructs() -> None:
|
||||
assert (
|
||||
SingletonPuzzles(singleton_mod_hash_pre_computed=None).singleton_mod_hash
|
||||
== SingletonPuzzles().singleton_mod_hash
|
||||
SingletonCorePuzzles(singleton_mod_hash_pre_computed=None).singleton_mod_hash
|
||||
== SingletonCorePuzzles().singleton_mod_hash
|
||||
)
|
||||
assert (
|
||||
SingletonPuzzles(singleton_launcher_hash_pre_computed=None).singleton_launcher_hash
|
||||
== SingletonPuzzles().singleton_launcher_hash
|
||||
SingletonCorePuzzles(singleton_launcher_hash_pre_computed=None).singleton_launcher_hash
|
||||
== SingletonCorePuzzles().singleton_launcher_hash
|
||||
)
|
||||
|
||||
@@ -11,7 +11,7 @@ from chia_rs.sized_bytes import bytes32
|
||||
from chia_rs.sized_ints import uint32, uint64
|
||||
|
||||
from chia._tests.environments.wallet import WalletStateTransition, WalletTestFramework
|
||||
from chia.pools.plotnft_drivers import PlotNFT, PoolConfig, UserConfig
|
||||
from chia.pools.plotnft_drivers import PlotNFT, PlotNFTInnerPuzzle, PoolConfig, UserConfig
|
||||
from chia.rpc.rpc_client import ResponseFailureError
|
||||
from chia.simulator.simulator_protocol import ReorgProtocol
|
||||
from chia.types.blockchain_format.program import Program
|
||||
@@ -409,7 +409,10 @@ async def test_plotnft_lifecycle(wallet_environments: WalletTestFramework, self_
|
||||
coin_spends += new_coin_spends
|
||||
singleton_coin_spend = next(iter(spend for spend in new_coin_spends if spend.coin.amount == 1))
|
||||
plotnft = PlotNFT.get_next_from_coin_spend(
|
||||
coin_spend=singleton_coin_spend, genesis_challenge=None, pre_uncurry=None, previous_plotnft_puzzle=plotnft
|
||||
coin_spend=singleton_coin_spend,
|
||||
genesis_challenge=None,
|
||||
pre_uncurry=None,
|
||||
previous_plotnft_puzzle=plotnft.inner_puzzle,
|
||||
)
|
||||
|
||||
NUM_CLAIMED = len(pool_rewards) - 1
|
||||
@@ -499,7 +502,7 @@ async def test_plotnft_lifecycle(wallet_environments: WalletTestFramework, self_
|
||||
# FINISH LEAVING (to new pool)
|
||||
plotnft = await plotnft_wallet.get_current_plotnft()
|
||||
await wallet_environments.full_node.farm_blocks_to_puzzlehash(
|
||||
count=plotnft.guaranteed_pool_config.heightlock + 2, guarantee_transaction_blocks=True
|
||||
count=plotnft.inner_puzzle.guaranteed_pool_config.heightlock + 2, guarantee_transaction_blocks=True
|
||||
)
|
||||
|
||||
await wallet_environments.full_node.wait_for_wallet_synced(env.node)
|
||||
@@ -648,7 +651,7 @@ async def test_plotnft_lifecycle(wallet_environments: WalletTestFramework, self_
|
||||
# FINISH LEAVING
|
||||
plotnft = await plotnft_wallet.get_current_plotnft()
|
||||
await wallet_environments.full_node.farm_blocks_to_puzzlehash(
|
||||
count=plotnft.guaranteed_pool_config.heightlock + 2, guarantee_transaction_blocks=True
|
||||
count=plotnft.inner_puzzle.guaranteed_pool_config.heightlock + 2, guarantee_transaction_blocks=True
|
||||
)
|
||||
await wallet_environments.full_node.wait_for_wallet_synced(env.node)
|
||||
|
||||
@@ -901,7 +904,7 @@ async def test_plotnft_errors(wallet_environments: WalletTestFramework, self_hos
|
||||
# farm to where completion should happen
|
||||
plotnft = await plotnft_wallet.get_current_plotnft()
|
||||
await wallet_environments.full_node.farm_blocks_to_puzzlehash(
|
||||
count=plotnft.guaranteed_pool_config.heightlock + 2, guarantee_transaction_blocks=True
|
||||
count=plotnft.inner_puzzle.guaranteed_pool_config.heightlock + 2, guarantee_transaction_blocks=True
|
||||
)
|
||||
|
||||
await wallet_environments.full_node.wait_for_wallet_synced(env.node)
|
||||
@@ -964,11 +967,14 @@ async def test_plotnft_errors(wallet_environments: WalletTestFramework, self_hos
|
||||
peer=Mock(),
|
||||
coin_data=PlotNFT(
|
||||
launcher_id=bytes32.zeros,
|
||||
genesis_challenge=bytes32.zeros,
|
||||
user_config=UserConfig(synthetic_pubkey=G1Element()), # the important bit
|
||||
exiting=False,
|
||||
inner_puzzle=PlotNFTInnerPuzzle(
|
||||
self_launcher_id=bytes32.zeros,
|
||||
genesis_challenge=bytes32.zeros,
|
||||
user_config=UserConfig(synthetic_pubkey=G1Element()), # the important bit
|
||||
exiting=False,
|
||||
),
|
||||
coin=Mock(),
|
||||
singleton_lineage_proof=Mock(),
|
||||
lineage_proof=Mock(),
|
||||
),
|
||||
)
|
||||
plotnft_after_raise = await plotnft_wallet.get_current_plotnft()
|
||||
|
||||
+267
-303
@@ -3,7 +3,7 @@ from __future__ import annotations
|
||||
import dataclasses
|
||||
from dataclasses import dataclass, field, replace
|
||||
from functools import cached_property
|
||||
from typing import ClassVar
|
||||
from typing import TYPE_CHECKING, ClassVar, cast
|
||||
|
||||
from chia_rs import G1Element
|
||||
from chia_rs.chia_rs import Coin, CoinSpend
|
||||
@@ -19,29 +19,42 @@ from chia.wallet.conditions import (
|
||||
Condition,
|
||||
CreateCoin,
|
||||
CreateCoinAnnouncement,
|
||||
MessageParticipant,
|
||||
Remark,
|
||||
SendMessage,
|
||||
parse_conditions_non_consensus,
|
||||
)
|
||||
from chia.wallet.lineage_proof import LineageProof
|
||||
from chia.wallet.puzzles.custody.custody_architecture import (
|
||||
DelegatedPuzzleAndSolution,
|
||||
DelegatedPuzzleAndSolution as MIPSDelegatedPuzzleAndSolution,
|
||||
)
|
||||
from chia.wallet.puzzles.custody.custody_architecture import (
|
||||
MofN,
|
||||
ProvenSpend,
|
||||
PuzzleWithRestrictions,
|
||||
)
|
||||
from chia.wallet.puzzles.custody.member_puzzles import BLSWithTaprootMember, FixedPuzzleMember, SingletonMember
|
||||
from chia.wallet.puzzles.custody.member_puzzles import (
|
||||
BLSWithTaprootMember,
|
||||
FixedPuzzleMember,
|
||||
)
|
||||
from chia.wallet.puzzles.custody.restriction_utilities import ValidatorStackRestriction
|
||||
from chia.wallet.puzzles.custody.restrictions import FixedCreateCoinDestinations, Heightlock, SendMessageBanned
|
||||
from chia.wallet.puzzles.load_clvm import load_clvm_maybe_recompile
|
||||
from chia.wallet.puzzles.singleton_top_layer_v1_1 import (
|
||||
SINGLETON_LAUNCHER,
|
||||
SINGLETON_LAUNCHER_HASH,
|
||||
SINGLETON_MOD,
|
||||
SINGLETON_MOD_HASH,
|
||||
puzzle_for_singleton,
|
||||
solution_for_singleton,
|
||||
from chia.wallet.puzzles.puzzle_drivers import (
|
||||
DelegatedPuzzleAndSolution,
|
||||
InnerPuzzle,
|
||||
PuzzleWithPuzzleHash,
|
||||
UnknownPuzzle,
|
||||
UnknownSolution,
|
||||
)
|
||||
from chia.wallet.puzzles.singleton_drivers import (
|
||||
P2Singleton,
|
||||
P2SingletonPuzzle,
|
||||
Singleton,
|
||||
SingletonCorePuzzles,
|
||||
SingletonLaunchInfo,
|
||||
SingletonLaunchResult,
|
||||
SingletonPuzzle,
|
||||
SingletonSolution,
|
||||
SingletonStruct,
|
||||
)
|
||||
from chia.wallet.uncurried_puzzle import UncurriedPuzzle, uncurry_puzzle
|
||||
|
||||
@@ -57,45 +70,6 @@ def forward_to_pool_puzzle_hash_dpuz(pool_puzzle_hash: bytes32, pool_memoization
|
||||
return FORWARD_TO_POOL_PUZZLE_HASH_DELEGATED_PUZZLE.curry(pool_puzzle_hash, pool_memoization)
|
||||
|
||||
|
||||
@dataclass(kw_only=True, frozen=True)
|
||||
class SingletonPuzzles:
|
||||
singleton_mod: Program = field(default_factory=lambda: SINGLETON_MOD)
|
||||
singleton_mod_hash_pre_computed: bytes32 | None = SINGLETON_MOD_HASH
|
||||
singleton_launcher: Program = field(default_factory=lambda: SINGLETON_LAUNCHER)
|
||||
singleton_launcher_hash_pre_computed: bytes32 | None = SINGLETON_LAUNCHER_HASH
|
||||
|
||||
@cached_property
|
||||
def singleton_mod_hash(self) -> bytes32:
|
||||
if self.singleton_mod_hash_pre_computed is not None:
|
||||
return self.singleton_mod_hash_pre_computed
|
||||
else:
|
||||
return self.singleton_mod.get_tree_hash()
|
||||
|
||||
@cached_property
|
||||
def singleton_launcher_hash(self) -> bytes32:
|
||||
if self.singleton_launcher_hash_pre_computed is not None:
|
||||
return self.singleton_launcher_hash_pre_computed
|
||||
else:
|
||||
return self.singleton_launcher.get_tree_hash()
|
||||
|
||||
|
||||
@dataclass(kw_only=True, frozen=True)
|
||||
class SingletonStruct:
|
||||
launcher_id: bytes32
|
||||
singleton_puzzles: SingletonPuzzles = SingletonPuzzles()
|
||||
|
||||
def to_program(self) -> Program:
|
||||
return Program.to(
|
||||
(
|
||||
self.singleton_puzzles.singleton_mod_hash,
|
||||
(self.launcher_id, self.singleton_puzzles.singleton_launcher_hash),
|
||||
)
|
||||
)
|
||||
|
||||
def struct_hash(self) -> bytes32:
|
||||
return self.to_program().get_tree_hash()
|
||||
|
||||
|
||||
@dataclass(kw_only=True, frozen=True)
|
||||
class PoolConfig:
|
||||
pool_puzzle_hash: bytes32
|
||||
@@ -109,18 +83,31 @@ class UserConfig:
|
||||
|
||||
|
||||
@dataclass(kw_only=True, frozen=True)
|
||||
class PlotNFTPuzzle:
|
||||
launcher_id: bytes32
|
||||
genesis_challenge: bytes32
|
||||
class PlotNFTInnerPuzzle(PuzzleWithPuzzleHash):
|
||||
if TYPE_CHECKING:
|
||||
_outer_puzzle_protocol_check: ClassVar[InnerPuzzle] = cast("PlotNFTInnerPuzzle", None)
|
||||
|
||||
user_config: UserConfig
|
||||
exiting: bool
|
||||
exiting: bool | None = None
|
||||
self_launcher_id: bytes32 | None = None
|
||||
genesis_challenge: bytes32 | None = None
|
||||
pool_config: PoolConfig | None = None
|
||||
singleton_puzzles: ClassVar[SingletonPuzzles] = SingletonPuzzles()
|
||||
singleton_puzzles: ClassVar[SingletonCorePuzzles] = SingletonCorePuzzles()
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
if self.pool_config is not None and (
|
||||
self.self_launcher_id is None or self.genesis_challenge is None or self.exiting is None
|
||||
):
|
||||
raise ValueError("Trying to initialize a pooling PlotNFT without required information")
|
||||
if self.pool_config is None and self.exiting:
|
||||
raise ValueError("Cannot initialize a PlotNFTPuzzle with an empty pool config and exiting=True")
|
||||
|
||||
@property
|
||||
def launcher_id(self) -> bytes32:
|
||||
if self.self_launcher_id is None:
|
||||
raise ValueError("Launcher ID is not present because PlotNFT is not pooling")
|
||||
return self.self_launcher_id
|
||||
|
||||
@property
|
||||
def singleton_struct(self) -> SingletonStruct:
|
||||
return SingletonStruct(launcher_id=self.launcher_id, singleton_puzzles=self.singleton_puzzles)
|
||||
@@ -139,36 +126,38 @@ class PlotNFTPuzzle:
|
||||
def bls_member(self) -> BLSWithTaprootMember:
|
||||
return BLSWithTaprootMember(synthetic_key=self.user_config.synthetic_pubkey)
|
||||
|
||||
def reward_puzhash(self) -> bytes32:
|
||||
return RewardPuzzle(singleton_id=self.launcher_id).puzzle_hash()
|
||||
|
||||
@cached_property
|
||||
def forward_pool_reward_dpuz(self) -> Program:
|
||||
return forward_to_pool_puzzle_hash_dpuz(
|
||||
self.guaranteed_pool_config.pool_puzzle_hash, self.guaranteed_pool_config.pool_memoization
|
||||
)
|
||||
|
||||
@property
|
||||
def waiting_room_puzzle(self) -> Self:
|
||||
return dataclasses.replace(self, exiting=True)
|
||||
|
||||
@cached_property
|
||||
def claim_pool_reward_dpuz(self) -> Program:
|
||||
assert self.genesis_challenge is not None
|
||||
return CLAIM_POOL_REWARDS_DELEGATED_PUZZLE.curry(
|
||||
self.genesis_challenge[:16],
|
||||
self.singleton_struct.singleton_puzzles.singleton_mod_hash,
|
||||
self.singleton_struct.struct_hash(),
|
||||
self.reward_puzhash(),
|
||||
self.forward_pool_reward_dpuz().get_tree_hash(),
|
||||
self.singleton_puzzles.singleton_mod_hash,
|
||||
self.singleton_struct.struct_hash,
|
||||
P2SingletonPuzzle(singleton_id=self.launcher_id).puzzle_hash,
|
||||
self.forward_pool_reward_dpuz.get_tree_hash(),
|
||||
)
|
||||
|
||||
def claim_pool_reward_dpuz_and_solution(self, reward: PoolReward) -> DelegatedPuzzleAndSolution:
|
||||
return DelegatedPuzzleAndSolution(
|
||||
puzzle=self.claim_pool_reward_dpuz(),
|
||||
solution=Program.to([self.inner_puzzle_hash(), reward.height, reward.coin.amount]),
|
||||
puzzle=UnknownPuzzle(known_puzzle=self.claim_pool_reward_dpuz),
|
||||
solution=UnknownSolution(Program.to([self.puzzle_hash, reward.height, reward.coin.amount])),
|
||||
)
|
||||
|
||||
@property
|
||||
def user_restriction(self) -> ValidatorStackRestriction:
|
||||
return ValidatorStackRestriction(
|
||||
required_wrappers=[
|
||||
FixedCreateCoinDestinations(allowed_ph=self.waiting_room_puzzle().inner_puzzle().get_tree_hash()),
|
||||
FixedCreateCoinDestinations(allowed_ph=self.waiting_room_puzzle.puzzle_hash),
|
||||
SendMessageBanned(),
|
||||
]
|
||||
if not self.exiting
|
||||
@@ -178,51 +167,63 @@ class PlotNFTPuzzle:
|
||||
def modify_delegated_puzzle_and_solution(
|
||||
self, delegated_puzzle_and_solution: DelegatedPuzzleAndSolution
|
||||
) -> DelegatedPuzzleAndSolution:
|
||||
return self.user_restriction().modify_delegated_puzzle_and_solution(
|
||||
delegated_puzzle_and_solution, [Program.to(None), Program.to(None)]
|
||||
modified_dpuz_and_solution = self.user_restriction.modify_delegated_puzzle_and_solution(
|
||||
MIPSDelegatedPuzzleAndSolution(
|
||||
puzzle=delegated_puzzle_and_solution.puzzle.puzzle,
|
||||
solution=delegated_puzzle_and_solution.solution.as_program(),
|
||||
),
|
||||
[Program.to(None), Program.to(None)],
|
||||
)
|
||||
return DelegatedPuzzleAndSolution(
|
||||
puzzle=UnknownPuzzle(known_puzzle=modified_dpuz_and_solution.puzzle),
|
||||
solution=UnknownSolution(solution=modified_dpuz_and_solution.solution),
|
||||
)
|
||||
|
||||
@property
|
||||
def user_puzzle_with_restrictions(self) -> PuzzleWithRestrictions:
|
||||
return PuzzleWithRestrictions(
|
||||
nonce=0,
|
||||
restrictions=[self.user_restriction()],
|
||||
restrictions=[self.user_restriction],
|
||||
puzzle=self.bls_member,
|
||||
)
|
||||
|
||||
def user_proven_spend(self, premodified_dpuz: Program) -> dict[bytes32, ProvenSpend]:
|
||||
return {
|
||||
self.user_puzzle_with_restrictions().puzzle_hash(_top_level=False): ProvenSpend(
|
||||
puzzle_reveal=self.user_puzzle_with_restrictions().puzzle_reveal(_top_level=False),
|
||||
solution=self.user_puzzle_with_restrictions().solve(
|
||||
self.user_puzzle_with_restrictions.puzzle_hash(_top_level=False): ProvenSpend(
|
||||
puzzle_reveal=self.user_puzzle_with_restrictions.puzzle_reveal(_top_level=False),
|
||||
solution=self.user_puzzle_with_restrictions.solve(
|
||||
member_validator_solutions=[],
|
||||
dpuz_validator_solutions=[self.user_restriction().solve(premodified_dpuz)],
|
||||
dpuz_validator_solutions=[self.user_restriction.solve(premodified_dpuz)],
|
||||
member_solution=self.bls_member.solve(),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@property
|
||||
def fixed_puzzle_member(self) -> FixedPuzzleMember:
|
||||
return FixedPuzzleMember(fixed_puzzle_hash=self.claim_pool_reward_dpuz().get_tree_hash())
|
||||
return FixedPuzzleMember(fixed_puzzle_hash=self.claim_pool_reward_dpuz.get_tree_hash())
|
||||
|
||||
@property
|
||||
def pool_puzzle_with_restrictions(self) -> PuzzleWithRestrictions:
|
||||
return PuzzleWithRestrictions(
|
||||
nonce=0,
|
||||
restrictions=[],
|
||||
puzzle=self.fixed_puzzle_member(),
|
||||
puzzle=self.fixed_puzzle_member,
|
||||
)
|
||||
|
||||
def pool_proven_spend(self) -> dict[bytes32, ProvenSpend]:
|
||||
return {
|
||||
self.pool_puzzle_with_restrictions().puzzle_hash(_top_level=False): ProvenSpend(
|
||||
puzzle_reveal=self.pool_puzzle_with_restrictions().puzzle_reveal(_top_level=False),
|
||||
solution=self.pool_puzzle_with_restrictions().solve(
|
||||
self.pool_puzzle_with_restrictions.puzzle_hash(_top_level=False): ProvenSpend(
|
||||
puzzle_reveal=self.pool_puzzle_with_restrictions.puzzle_reveal(_top_level=False),
|
||||
solution=self.pool_puzzle_with_restrictions.solve(
|
||||
member_validator_solutions=[],
|
||||
dpuz_validator_solutions=[],
|
||||
member_solution=self.fixed_puzzle_member().solve(),
|
||||
member_solution=self.fixed_puzzle_member.solve(),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@property
|
||||
def puzzle_with_restrictions(self) -> PuzzleWithRestrictions:
|
||||
return PuzzleWithRestrictions(
|
||||
nonce=0,
|
||||
@@ -230,18 +231,20 @@ class PlotNFTPuzzle:
|
||||
puzzle=MofN(
|
||||
m=1,
|
||||
members=[
|
||||
self.user_puzzle_with_restrictions(),
|
||||
self.pool_puzzle_with_restrictions(),
|
||||
self.user_puzzle_with_restrictions,
|
||||
self.pool_puzzle_with_restrictions,
|
||||
],
|
||||
)
|
||||
if self.pooling
|
||||
else self.bls_member,
|
||||
additional_memos=self.additional_memos(),
|
||||
additional_memos=self.additional_memos,
|
||||
)
|
||||
|
||||
@property
|
||||
def memo(self) -> Program:
|
||||
return self.puzzle_with_restrictions().memo()
|
||||
return self.puzzle_with_restrictions.memo()
|
||||
|
||||
@property
|
||||
def additional_memos(self) -> Program:
|
||||
if self.pooling:
|
||||
return Program.to(
|
||||
@@ -255,80 +258,106 @@ class PlotNFTPuzzle:
|
||||
else:
|
||||
return Program.to([self.bls_member.synthetic_key])
|
||||
|
||||
def inner_puzzle(self) -> Program:
|
||||
return self.puzzle_with_restrictions().puzzle_reveal()
|
||||
@property
|
||||
def puzzle(self) -> Program:
|
||||
return self.puzzle_with_restrictions.puzzle_reveal()
|
||||
|
||||
def inner_puzzle_hash(self) -> bytes32:
|
||||
return self.inner_puzzle().get_tree_hash()
|
||||
|
||||
def puzzle(self, nonce: int) -> Program:
|
||||
return puzzle_for_singleton(
|
||||
launcher_id=self.singleton_struct.launcher_id,
|
||||
launcher_hash=self.singleton_struct.singleton_puzzles.singleton_launcher_hash,
|
||||
singleton_mod=self.singleton_struct.singleton_puzzles.singleton_mod,
|
||||
singleton_mod_hash=self.singleton_struct.singleton_puzzles.singleton_mod_hash,
|
||||
inner_puz=self.puzzle_with_restrictions().puzzle_reveal(),
|
||||
)
|
||||
|
||||
def puzzle_hash(self, nonce: int) -> bytes32:
|
||||
return self.puzzle(nonce).get_tree_hash()
|
||||
@property
|
||||
def puzzle_hash(self) -> bytes32:
|
||||
return self.puzzle.get_tree_hash()
|
||||
|
||||
def forward_pool_reward_inner_solution(self, reward: PoolReward) -> Program:
|
||||
custody_pwr = self.puzzle_with_restrictions()
|
||||
custody_pwr = self.puzzle_with_restrictions
|
||||
assert isinstance(custody_pwr.puzzle, MofN)
|
||||
dp_and_sol = self.claim_pool_reward_dpuz_and_solution(reward)
|
||||
return custody_pwr.solve(
|
||||
member_validator_solutions=[],
|
||||
dpuz_validator_solutions=[],
|
||||
member_solution=custody_pwr.puzzle.solve(self.pool_proven_spend()),
|
||||
delegated_puzzle_and_solution=self.claim_pool_reward_dpuz_and_solution(reward),
|
||||
delegated_puzzle_and_solution=MIPSDelegatedPuzzleAndSolution(
|
||||
puzzle=dp_and_sol.puzzle.puzzle, solution=dp_and_sol.solution.as_program()
|
||||
),
|
||||
)
|
||||
|
||||
def exit_to_from_waiting_room_inner_solution(
|
||||
self, delegated_puzzle_and_solution: DelegatedPuzzleAndSolution
|
||||
) -> Program:
|
||||
custody_pwr = self.puzzle_with_restrictions()
|
||||
custody_pwr = self.puzzle_with_restrictions
|
||||
assert isinstance(custody_pwr.puzzle, MofN)
|
||||
return custody_pwr.solve(
|
||||
member_validator_solutions=[],
|
||||
dpuz_validator_solutions=[],
|
||||
member_solution=custody_pwr.puzzle.solve(self.user_proven_spend(delegated_puzzle_and_solution.puzzle)),
|
||||
delegated_puzzle_and_solution=self.user_restriction().modify_delegated_puzzle_and_solution(
|
||||
delegated_puzzle_and_solution, [Program.to([]), Program.to([])]
|
||||
member_solution=custody_pwr.puzzle.solve(
|
||||
self.user_proven_spend(delegated_puzzle_and_solution.puzzle.puzzle)
|
||||
),
|
||||
delegated_puzzle_and_solution=self.user_restriction.modify_delegated_puzzle_and_solution(
|
||||
MIPSDelegatedPuzzleAndSolution(
|
||||
puzzle=delegated_puzzle_and_solution.puzzle.puzzle,
|
||||
solution=delegated_puzzle_and_solution.solution.as_program(),
|
||||
),
|
||||
[Program.to([]), Program.to([])],
|
||||
),
|
||||
)
|
||||
|
||||
@property
|
||||
def exit_to_waiting_room_condition(self) -> CreateCoin:
|
||||
return CreateCoin(
|
||||
puzzle_hash=self.waiting_room_puzzle().inner_puzzle_hash(),
|
||||
puzzle_hash=self.waiting_room_puzzle.puzzle_hash,
|
||||
amount=uint64(1),
|
||||
memos=[self.singleton_struct.struct_hash()],
|
||||
memos=[self.singleton_struct.struct_hash],
|
||||
)
|
||||
|
||||
@property
|
||||
def exit_from_waiting_room_conditions(self) -> tuple[AssertHeightRelative, CreateCoin]:
|
||||
next_plotnft_puzzle = replace(self, pool_config=None, exiting=False)
|
||||
return (
|
||||
AssertHeightRelative(height=self.guaranteed_pool_config.heightlock),
|
||||
CreateCoin(
|
||||
puzzle_hash=next_plotnft_puzzle.inner_puzzle_hash(),
|
||||
puzzle_hash=next_plotnft_puzzle.puzzle_hash,
|
||||
amount=uint64(1),
|
||||
# maybe the full memo is not strictly necessary, but it's needed for robustness at the moment
|
||||
memo_blob=Program.to((self.singleton_struct.struct_hash(), next_plotnft_puzzle.memo())),
|
||||
memo_blob=Program.to((self.singleton_struct.struct_hash, next_plotnft_puzzle.memo)),
|
||||
),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def match(cls, *, unknown_puzzle: UnknownPuzzle, solution: object | None = None) -> PlotNFTInnerPuzzle | None:
|
||||
mips_match = PuzzleWithRestrictions.match(unknown_puzzle=unknown_puzzle, solution=solution)
|
||||
if mips_match is None:
|
||||
return None
|
||||
assert isinstance(mips_match, PuzzleWithRestrictions)
|
||||
assert isinstance(mips_match.puzzle, UnknownPuzzle)
|
||||
potential_bls_member_match = BLSWithTaprootMember.match(unknown_puzzle=mips_match.puzzle)
|
||||
if potential_bls_member_match is not None:
|
||||
return PlotNFTInnerPuzzle(
|
||||
user_config=UserConfig(synthetic_pubkey=potential_bls_member_match.guaranteed_synthetic_key)
|
||||
)
|
||||
potential_mofn_match = MofN.match(unknown_puzzle=mips_match.puzzle, solution=solution)
|
||||
if potential_mofn_match is None:
|
||||
return None
|
||||
if MofN.m != 2:
|
||||
return None
|
||||
raise NotImplementedError("Currently unimplemented")
|
||||
|
||||
|
||||
class GetNextPlotNFTError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
@dataclass(kw_only=True, frozen=True)
|
||||
class PlotNFT(PlotNFTPuzzle):
|
||||
class PlotNFTLaunchResult(SingletonLaunchResult[PlotNFTInnerPuzzle]):
|
||||
necessary_conditions: list[Condition]
|
||||
necessary_spends: list[CoinSpend]
|
||||
launched_singleton: PlotNFT
|
||||
|
||||
|
||||
@dataclass(kw_only=True, frozen=True)
|
||||
class PlotNFT(Singleton[PlotNFTInnerPuzzle]):
|
||||
coin: Coin
|
||||
singleton_lineage_proof: LineageProof
|
||||
remarks: list[Remark] = field(default_factory=list)
|
||||
|
||||
@classmethod
|
||||
def launch(
|
||||
def launch_plotnft(
|
||||
cls,
|
||||
*,
|
||||
origin_coins: list[Coin],
|
||||
@@ -338,13 +367,13 @@ class PlotNFT(PlotNFTPuzzle):
|
||||
pool_config: PoolConfig | None = None,
|
||||
exiting: bool = False,
|
||||
remark: Remark | None = None,
|
||||
) -> tuple[tuple[AssertCoinAnnouncement, AssertCoinAnnouncement], list[CoinSpend], Self]:
|
||||
) -> PlotNFTLaunchResult:
|
||||
origin_coin = origin_coins[0]
|
||||
launcher_coin = Coin(origin_coin.name(), cls.singleton_puzzles.singleton_launcher_hash, uint64(1))
|
||||
launcher_id = launcher_coin.name()
|
||||
|
||||
plotnft_puzzle = PlotNFTPuzzle(
|
||||
launcher_id=launcher_id,
|
||||
plotnft_inner_puzzle = PlotNFTInnerPuzzle(
|
||||
self_launcher_id=launcher_id,
|
||||
user_config=user_config,
|
||||
pool_config=pool_config,
|
||||
exiting=exiting,
|
||||
@@ -355,59 +384,49 @@ class PlotNFT(PlotNFTPuzzle):
|
||||
1,
|
||||
[
|
||||
CreateCoin(
|
||||
plotnft_puzzle.inner_puzzle_hash(),
|
||||
plotnft_inner_puzzle.puzzle_hash,
|
||||
uint64(1),
|
||||
memo_blob=Program.to((hint, plotnft_puzzle.puzzle_with_restrictions().memo())),
|
||||
memo_blob=Program.to((hint, plotnft_inner_puzzle.puzzle_with_restrictions.memo())),
|
||||
).to_program(),
|
||||
CreateCoinAnnouncement(msg=b"").to_program(),
|
||||
*([] if remark is None else [remark.to_program()]),
|
||||
],
|
||||
)
|
||||
)
|
||||
full_rev_singleton_puzzle = puzzle_for_singleton(
|
||||
launcher_id,
|
||||
rev_puzzle,
|
||||
singleton_mod=cls.singleton_puzzles.singleton_mod,
|
||||
launcher_hash=cls.singleton_puzzles.singleton_launcher_hash,
|
||||
singleton_mod_hash=cls.singleton_puzzles.singleton_mod_hash,
|
||||
)
|
||||
rev_coin = Coin(launcher_id, full_rev_singleton_puzzle.get_tree_hash(), uint64(1))
|
||||
rev_coin_id = rev_coin.name()
|
||||
launcher_solution = Program.to([full_rev_singleton_puzzle.get_tree_hash(), uint64(1), None])
|
||||
|
||||
conditions = (
|
||||
AssertCoinAnnouncement(asserted_id=launcher_id, asserted_msg=launcher_solution.get_tree_hash()),
|
||||
AssertCoinAnnouncement(asserted_id=rev_coin_id, asserted_msg=b""),
|
||||
)
|
||||
launcher_spend = make_spend(
|
||||
launcher_coin,
|
||||
cls.singleton_puzzles.singleton_launcher,
|
||||
launcher_solution,
|
||||
)
|
||||
rev_spend = make_spend(
|
||||
rev_coin,
|
||||
full_rev_singleton_puzzle,
|
||||
solution_for_singleton(
|
||||
LineageProof(parent_name=launcher_coin.parent_coin_info, amount=launcher_coin.amount),
|
||||
uint64(1),
|
||||
Program.to(None),
|
||||
pre_rev_launch_result = super().launch(
|
||||
origin_coin=origin_coin,
|
||||
launch_info=SingletonLaunchInfo(
|
||||
desired_inner_puzzle=UnknownPuzzle(known_puzzle=rev_puzzle), key_value_hints={}
|
||||
),
|
||||
)
|
||||
return (
|
||||
conditions,
|
||||
[launcher_spend, rev_spend],
|
||||
cls(
|
||||
coin=Coin(rev_coin_id, plotnft_puzzle.puzzle_hash(nonce=0), uint64(1)),
|
||||
singleton_lineage_proof=LineageProof(
|
||||
parent_name=rev_coin.parent_coin_info,
|
||||
inner_puzzle_hash=rev_puzzle.get_tree_hash(),
|
||||
amount=rev_coin.amount,
|
||||
rev_coin_id = pre_rev_launch_result.launched_singleton.coin.name()
|
||||
assert_rev_ca = AssertCoinAnnouncement(asserted_id=rev_coin_id, asserted_msg=b"")
|
||||
|
||||
rev_spend = make_spend(
|
||||
pre_rev_launch_result.launched_singleton.coin,
|
||||
pre_rev_launch_result.launched_singleton.puzzle,
|
||||
SingletonSolution(
|
||||
lineage_proof=LineageProof(parent_name=launcher_coin.parent_coin_info, amount=launcher_coin.amount),
|
||||
coin_amount=uint64(1),
|
||||
inner_solution=UnknownSolution(solution=Program.NIL),
|
||||
).as_program(),
|
||||
)
|
||||
return PlotNFTLaunchResult(
|
||||
necessary_conditions=[*pre_rev_launch_result.necessary_conditions, assert_rev_ca],
|
||||
necessary_spends=[*pre_rev_launch_result.necessary_spends, rev_spend],
|
||||
launched_singleton=cls(
|
||||
coin=Coin(
|
||||
rev_coin_id,
|
||||
SingletonPuzzle(launcher_id=launcher_id, inner_puzzle=plotnft_inner_puzzle).puzzle_hash,
|
||||
uint64(1),
|
||||
),
|
||||
launcher_id=launcher_id,
|
||||
user_config=user_config,
|
||||
pool_config=pool_config,
|
||||
exiting=exiting,
|
||||
genesis_challenge=genesis_challenge,
|
||||
lineage_proof=LineageProof(
|
||||
parent_name=pre_rev_launch_result.launched_singleton.coin.parent_coin_info,
|
||||
inner_puzzle_hash=rev_puzzle.get_tree_hash(),
|
||||
amount=pre_rev_launch_result.launched_singleton.coin.amount,
|
||||
),
|
||||
inner_puzzle=plotnft_inner_puzzle,
|
||||
),
|
||||
)
|
||||
|
||||
@@ -418,7 +437,7 @@ class PlotNFT(PlotNFTPuzzle):
|
||||
coin_spend: CoinSpend,
|
||||
genesis_challenge: bytes32 | None = None,
|
||||
pre_uncurry: UncurriedPuzzle | None = None,
|
||||
previous_plotnft_puzzle: PlotNFTPuzzle | None = None,
|
||||
previous_plotnft_puzzle: PlotNFTInnerPuzzle | None = None,
|
||||
) -> Self:
|
||||
# some input validation
|
||||
if genesis_challenge is None and previous_plotnft_puzzle is None:
|
||||
@@ -463,13 +482,11 @@ class PlotNFT(PlotNFTPuzzle):
|
||||
and previous_plotnft_puzzle.pool_config is not None
|
||||
):
|
||||
if (
|
||||
replace(previous_plotnft_puzzle, pool_config=None, exiting=False).inner_puzzle_hash()
|
||||
replace(previous_plotnft_puzzle, pool_config=None, exiting=False).puzzle_hash
|
||||
== singleton_create_coin.puzzle_hash
|
||||
):
|
||||
plotnft_puzzle = replace(previous_plotnft_puzzle, pool_config=None, exiting=False)
|
||||
elif (
|
||||
replace(previous_plotnft_puzzle, exiting=True).inner_puzzle_hash() == singleton_create_coin.puzzle_hash
|
||||
):
|
||||
elif replace(previous_plotnft_puzzle, exiting=True).puzzle_hash == singleton_create_coin.puzzle_hash:
|
||||
plotnft_puzzle = replace(previous_plotnft_puzzle, exiting=True)
|
||||
|
||||
# Finally, we try to look for the memos
|
||||
@@ -500,217 +517,164 @@ class PlotNFT(PlotNFTPuzzle):
|
||||
pool_config = None
|
||||
exiting = False
|
||||
|
||||
plotnft_puzzle = PlotNFTPuzzle(
|
||||
launcher_id=launcher_id,
|
||||
plotnft_puzzle = PlotNFTInnerPuzzle(
|
||||
self_launcher_id=launcher_id,
|
||||
user_config=UserConfig(synthetic_pubkey=pubkey),
|
||||
pool_config=pool_config,
|
||||
exiting=exiting,
|
||||
genesis_challenge=genesis_challenge,
|
||||
)
|
||||
if plotnft_puzzle.inner_puzzle_hash() != singleton_create_coin.puzzle_hash:
|
||||
if plotnft_puzzle.puzzle_hash != singleton_create_coin.puzzle_hash:
|
||||
raise GetNextPlotNFTError("Invalid memoization of PlotNFT")
|
||||
|
||||
return cls(
|
||||
coin=Coin(
|
||||
coin_spend.coin.name(),
|
||||
plotnft_puzzle.puzzle(nonce=0).get_tree_hash(),
|
||||
SingletonPuzzle(launcher_id=launcher_id, inner_puzzle=plotnft_puzzle).puzzle_hash,
|
||||
coin_spend.coin.amount,
|
||||
),
|
||||
singleton_lineage_proof=LineageProof(
|
||||
lineage_proof=LineageProof(
|
||||
parent_name=coin_spend.coin.parent_coin_info,
|
||||
inner_puzzle_hash=inner_puzzle.get_tree_hash(),
|
||||
amount=coin_spend.coin.amount,
|
||||
),
|
||||
inner_puzzle=plotnft_puzzle,
|
||||
launcher_id=launcher_id,
|
||||
user_config=plotnft_puzzle.user_config,
|
||||
pool_config=plotnft_puzzle.pool_config,
|
||||
exiting=plotnft_puzzle.exiting,
|
||||
genesis_challenge=genesis_challenge,
|
||||
remarks=remarks,
|
||||
)
|
||||
|
||||
def singleton_action_spend(self, inner_solution: Program) -> CoinSpend:
|
||||
return make_spend(
|
||||
coin=self.coin,
|
||||
puzzle_reveal=puzzle_for_singleton(
|
||||
launcher_id=self.singleton_struct.launcher_id,
|
||||
inner_puz=self.inner_puzzle(),
|
||||
singleton_mod=self.singleton_struct.singleton_puzzles.singleton_mod,
|
||||
singleton_mod_hash=self.singleton_struct.singleton_puzzles.singleton_mod_hash,
|
||||
launcher_hash=self.singleton_struct.singleton_puzzles.singleton_launcher_hash,
|
||||
),
|
||||
solution=solution_for_singleton(
|
||||
lineage_proof=self.singleton_lineage_proof,
|
||||
amount=self.coin.amount,
|
||||
inner_solution=inner_solution,
|
||||
),
|
||||
)
|
||||
|
||||
def forward_pool_reward(self, reward: PoolReward) -> list[CoinSpend]:
|
||||
if not self.pooling:
|
||||
if not self.inner_puzzle.pooling:
|
||||
raise ValueError("Cannot forward pool reward while self pooling. Try `claim_pool_rewards`")
|
||||
return [
|
||||
self.singleton_action_spend(inner_solution=self.forward_pool_reward_inner_solution(reward)),
|
||||
make_spend(
|
||||
coin=reward.coin,
|
||||
puzzle_reveal=reward.puzzle(),
|
||||
solution=reward.solve(
|
||||
self.inner_puzzle_hash(),
|
||||
delegated_puzzle_and_solution=DelegatedPuzzleAndSolution(
|
||||
puzzle=self.forward_pool_reward_dpuz(),
|
||||
solution=Program.to([reward.coin.amount]),
|
||||
),
|
||||
),
|
||||
),
|
||||
]
|
||||
coin_spend = self.spend(
|
||||
inner_solution=UnknownSolution(self.inner_puzzle.forward_pool_reward_inner_solution(reward))
|
||||
)
|
||||
reward_spends, _ = self.claim_p2_singletons(
|
||||
rewards_to_claim=[reward],
|
||||
reward_delegated_puzzles_and_solutions=[
|
||||
DelegatedPuzzleAndSolution(
|
||||
puzzle=UnknownPuzzle(known_puzzle=self.inner_puzzle.forward_pool_reward_dpuz),
|
||||
solution=UnknownSolution(Program.to([reward.coin.amount])),
|
||||
)
|
||||
],
|
||||
)
|
||||
return [coin_spend, *reward_spends]
|
||||
|
||||
def exit_to_waiting_room(self, delegated_puzzle_and_solution: DelegatedPuzzleAndSolution) -> list[CoinSpend]:
|
||||
if not self.pooling:
|
||||
if not self.inner_puzzle.pooling:
|
||||
raise ValueError("Cannot exit to waiting room while self pooling.")
|
||||
if self.exiting:
|
||||
if self.inner_puzzle.exiting:
|
||||
raise ValueError("Already exiting to waiting room, cannot exit again")
|
||||
return [
|
||||
self.singleton_action_spend(
|
||||
inner_solution=self.exit_to_from_waiting_room_inner_solution(delegated_puzzle_and_solution)
|
||||
coin_spend = self.spend(
|
||||
inner_solution=UnknownSolution(
|
||||
solution=self.inner_puzzle.exit_to_from_waiting_room_inner_solution(delegated_puzzle_and_solution)
|
||||
)
|
||||
]
|
||||
)
|
||||
return [coin_spend]
|
||||
|
||||
def exit_waiting_room(self, delegated_puzzle_and_solution: DelegatedPuzzleAndSolution) -> list[CoinSpend]:
|
||||
if not self.pooling:
|
||||
if not self.inner_puzzle.pooling:
|
||||
raise ValueError("Cannot exit waiting room while self pooling.")
|
||||
if not self.exiting:
|
||||
if not self.inner_puzzle.exiting:
|
||||
raise ValueError("Cannot exit waiting room while not in it")
|
||||
return [
|
||||
self.singleton_action_spend(
|
||||
inner_solution=self.exit_to_from_waiting_room_inner_solution(delegated_puzzle_and_solution)
|
||||
coin_spend = self.spend(
|
||||
inner_solution=UnknownSolution(
|
||||
solution=self.inner_puzzle.exit_to_from_waiting_room_inner_solution(delegated_puzzle_and_solution)
|
||||
)
|
||||
]
|
||||
)
|
||||
return [coin_spend]
|
||||
|
||||
def claim_pool_rewards(
|
||||
self,
|
||||
rewards_to_claim: list[PoolReward],
|
||||
reward_delegated_puzzles_and_solutions: list[DelegatedPuzzleAndSolution],
|
||||
) -> list[CoinSpend]:
|
||||
if self.pooling:
|
||||
if self.inner_puzzle.pooling:
|
||||
raise ValueError("Cannot claim rewards while pooling. If you're a pool, try `forward_pool_rewards`")
|
||||
if len(rewards_to_claim) != len(reward_delegated_puzzles_and_solutions):
|
||||
raise ValueError("Number of rewards and delegated puzzles and solutions must match")
|
||||
reward_spends, messages = self.claim_p2_singletons(
|
||||
rewards_to_claim=rewards_to_claim,
|
||||
reward_delegated_puzzles_and_solutions=reward_delegated_puzzles_and_solutions,
|
||||
)
|
||||
dpuz_and_solution = DelegatedPuzzleAndSolution(
|
||||
puzzle=Program.to(
|
||||
(
|
||||
1,
|
||||
[
|
||||
CreateCoin(
|
||||
puzzle_hash=self.inner_puzzle_hash(),
|
||||
amount=self.coin.amount,
|
||||
memos=[self.singleton_struct.struct_hash()],
|
||||
).to_program(),
|
||||
*(
|
||||
SendMessage(
|
||||
msg=dpuz_and_sol.puzzle.get_tree_hash(),
|
||||
sender=MessageParticipant(puzzle_hash_committed=self.puzzle_hash(nonce=0)),
|
||||
receiver=MessageParticipant(coin_id_committed=reward.coin.name()),
|
||||
).to_program()
|
||||
for reward, dpuz_and_sol in zip(rewards_to_claim, reward_delegated_puzzles_and_solutions)
|
||||
),
|
||||
],
|
||||
puzzle=UnknownPuzzle(
|
||||
known_puzzle=Program.to(
|
||||
(
|
||||
1,
|
||||
[
|
||||
CreateCoin(
|
||||
puzzle_hash=self.inner_puzzle.puzzle_hash,
|
||||
amount=self.coin.amount,
|
||||
memos=[self.singleton_struct.struct_hash],
|
||||
).to_program(),
|
||||
*[msg.to_program() for msg in messages],
|
||||
],
|
||||
)
|
||||
)
|
||||
),
|
||||
solution=Program.to([]),
|
||||
solution=UnknownSolution(solution=Program.to([])),
|
||||
)
|
||||
return [
|
||||
self.singleton_action_spend(
|
||||
inner_solution=self.puzzle_with_restrictions().solve(
|
||||
coin_spend = self.spend(
|
||||
inner_solution=UnknownSolution(
|
||||
solution=self.inner_puzzle.puzzle_with_restrictions.solve(
|
||||
member_validator_solutions=[],
|
||||
dpuz_validator_solutions=[],
|
||||
member_solution=self.bls_member.solve(),
|
||||
delegated_puzzle_and_solution=dpuz_and_solution,
|
||||
)
|
||||
),
|
||||
*(
|
||||
make_spend(
|
||||
coin=reward.coin,
|
||||
puzzle_reveal=reward.puzzle(),
|
||||
solution=reward.solve(
|
||||
self.inner_puzzle_hash(),
|
||||
delegated_puzzle_and_solution=dpuz_and_sol,
|
||||
member_solution=self.inner_puzzle.bls_member.solve(),
|
||||
delegated_puzzle_and_solution=MIPSDelegatedPuzzleAndSolution(
|
||||
puzzle=dpuz_and_solution.puzzle.puzzle, solution=dpuz_and_solution.solution.as_program()
|
||||
),
|
||||
)
|
||||
for reward, dpuz_and_sol in zip(rewards_to_claim, reward_delegated_puzzles_and_solutions)
|
||||
),
|
||||
]
|
||||
)
|
||||
)
|
||||
return [coin_spend, *reward_spends]
|
||||
|
||||
def join_pool(
|
||||
self, user_config: UserConfig, pool_config: PoolConfig, extra_conditions: tuple[Condition, ...] = tuple()
|
||||
) -> list[CoinSpend]:
|
||||
plotnft_puzzle = PlotNFTPuzzle(
|
||||
launcher_id=self.launcher_id,
|
||||
plotnft_puzzle = PlotNFTInnerPuzzle(
|
||||
self_launcher_id=self.launcher_id,
|
||||
user_config=user_config,
|
||||
pool_config=pool_config,
|
||||
exiting=False,
|
||||
genesis_challenge=self.genesis_challenge,
|
||||
genesis_challenge=self.inner_puzzle.genesis_challenge,
|
||||
)
|
||||
|
||||
dpuz_and_solution = DelegatedPuzzleAndSolution(
|
||||
puzzle=Program.to(
|
||||
(
|
||||
1,
|
||||
[
|
||||
CreateCoin(
|
||||
plotnft_puzzle.inner_puzzle_hash(),
|
||||
amount=self.coin.amount,
|
||||
memo_blob=Program.to((self.singleton_struct.struct_hash(), plotnft_puzzle.memo())),
|
||||
).to_program(),
|
||||
*(cond.to_program() for cond in extra_conditions),
|
||||
],
|
||||
puzzle=UnknownPuzzle(
|
||||
known_puzzle=Program.to(
|
||||
(
|
||||
1,
|
||||
[
|
||||
CreateCoin(
|
||||
plotnft_puzzle.puzzle_hash,
|
||||
amount=self.coin.amount,
|
||||
memo_blob=Program.to((self.singleton_struct.struct_hash, plotnft_puzzle.memo)),
|
||||
).to_program(),
|
||||
*(cond.to_program() for cond in extra_conditions),
|
||||
],
|
||||
)
|
||||
)
|
||||
),
|
||||
solution=Program.to([]),
|
||||
solution=UnknownSolution(solution=Program.to([])),
|
||||
)
|
||||
return [
|
||||
self.singleton_action_spend(
|
||||
inner_solution=self.puzzle_with_restrictions().solve(
|
||||
coin_spend = self.spend(
|
||||
inner_solution=UnknownSolution(
|
||||
self.inner_puzzle.puzzle_with_restrictions.solve(
|
||||
member_validator_solutions=[],
|
||||
dpuz_validator_solutions=[],
|
||||
member_solution=self.bls_member.solve(),
|
||||
delegated_puzzle_and_solution=dpuz_and_solution,
|
||||
member_solution=self.inner_puzzle.bls_member.solve(),
|
||||
delegated_puzzle_and_solution=MIPSDelegatedPuzzleAndSolution(
|
||||
puzzle=dpuz_and_solution.puzzle.puzzle, solution=dpuz_and_solution.solution.as_program()
|
||||
),
|
||||
)
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
@dataclass(kw_only=True, frozen=True)
|
||||
class RewardPuzzle:
|
||||
singleton_id: bytes32
|
||||
|
||||
@property
|
||||
def singleton_member(self) -> SingletonMember:
|
||||
return SingletonMember(singleton_id=self.singleton_id)
|
||||
|
||||
def puzzle_with_restrictions(self) -> PuzzleWithRestrictions:
|
||||
return PuzzleWithRestrictions(nonce=0, restrictions=[], puzzle=self.singleton_member)
|
||||
|
||||
def puzzle(self) -> Program:
|
||||
return self.puzzle_with_restrictions().puzzle_reveal()
|
||||
|
||||
def puzzle_hash(self) -> bytes32:
|
||||
return self.puzzle().get_tree_hash()
|
||||
|
||||
def solve(
|
||||
self, singleton_inner_puzzle_hash: bytes32, delegated_puzzle_and_solution: DelegatedPuzzleAndSolution
|
||||
) -> Program:
|
||||
return self.puzzle_with_restrictions().solve(
|
||||
[],
|
||||
[],
|
||||
self.singleton_member.solve(singleton_inner_puzzle_hash),
|
||||
delegated_puzzle_and_solution,
|
||||
)
|
||||
return [coin_spend]
|
||||
|
||||
|
||||
@dataclass(kw_only=True, frozen=True)
|
||||
class PoolReward(RewardPuzzle):
|
||||
coin: Coin
|
||||
|
||||
class PoolReward(P2Singleton):
|
||||
@property
|
||||
def height(self) -> uint32:
|
||||
return uint32.from_bytes(self.coin.parent_coin_info[28:])
|
||||
|
||||
@@ -7,7 +7,7 @@ from chia_rs.sized_bytes import bytes32
|
||||
from chia_rs.sized_ints import uint32, uint64
|
||||
from typing_extensions import Self
|
||||
|
||||
from chia.pools.plotnft_drivers import PlotNFT, PoolConfig, PoolReward, UserConfig
|
||||
from chia.pools.plotnft_drivers import PlotNFT, PlotNFTInnerPuzzle, PoolConfig, PoolReward, UserConfig
|
||||
from chia.types.blockchain_format.program import Program
|
||||
from chia.util.db_wrapper import DBWrapper2
|
||||
from chia.wallet.conditions import Remark
|
||||
@@ -20,18 +20,21 @@ DEFAULT_POOL_REWARDS_PER_CLAIM = 20
|
||||
def _row_to_plotnft(row: Row, genesis_challenge: bytes32) -> PlotNFT:
|
||||
return PlotNFT(
|
||||
coin=Coin(parent_coin_info=bytes32(row[1]), puzzle_hash=bytes32(row[2]), amount=uint64.from_bytes(row[3])),
|
||||
singleton_lineage_proof=LineageProof.from_bytes(row[4]),
|
||||
lineage_proof=LineageProof.from_bytes(row[4]),
|
||||
launcher_id=bytes32(row[5]),
|
||||
user_config=UserConfig(synthetic_pubkey=G1Element.from_bytes(row[6])),
|
||||
pool_config=PoolConfig(
|
||||
pool_puzzle_hash=bytes32(row[7]),
|
||||
heightlock=uint32.from_bytes(row[8]),
|
||||
pool_memoization=Program.from_bytes(row[9]),
|
||||
)
|
||||
if row[7:10] != (b"", b"", b"")
|
||||
else None,
|
||||
genesis_challenge=genesis_challenge,
|
||||
exiting=False if row[10] == 0 else True,
|
||||
inner_puzzle=PlotNFTInnerPuzzle(
|
||||
self_launcher_id=bytes32(row[5]),
|
||||
user_config=UserConfig(synthetic_pubkey=G1Element.from_bytes(row[6])),
|
||||
pool_config=PoolConfig(
|
||||
pool_puzzle_hash=bytes32(row[7]),
|
||||
heightlock=uint32.from_bytes(row[8]),
|
||||
pool_memoization=Program.from_bytes(row[9]),
|
||||
)
|
||||
if row[7:10] != (b"", b"", b"")
|
||||
else None,
|
||||
genesis_challenge=genesis_challenge,
|
||||
exiting=False if row[10] == 0 else True,
|
||||
),
|
||||
remarks=[Remark(Program.to(row[11]))] if row[11] is not None else [],
|
||||
)
|
||||
|
||||
@@ -96,13 +99,19 @@ class PlotNFTStore:
|
||||
plotnft.coin.parent_coin_info,
|
||||
plotnft.coin.puzzle_hash,
|
||||
bytes(plotnft.coin.amount),
|
||||
bytes(plotnft.singleton_lineage_proof),
|
||||
bytes(plotnft.lineage_proof),
|
||||
plotnft.launcher_id,
|
||||
bytes(plotnft.user_config.synthetic_pubkey),
|
||||
plotnft.pool_config.pool_puzzle_hash if plotnft.pool_config is not None else b"",
|
||||
bytes(plotnft.pool_config.heightlock) if plotnft.pool_config is not None else b"",
|
||||
bytes(plotnft.pool_config.pool_memoization) if plotnft.pool_config is not None else b"",
|
||||
plotnft.exiting,
|
||||
bytes(plotnft.inner_puzzle.user_config.synthetic_pubkey),
|
||||
plotnft.inner_puzzle.pool_config.pool_puzzle_hash
|
||||
if plotnft.inner_puzzle.pool_config is not None
|
||||
else b"",
|
||||
bytes(plotnft.inner_puzzle.pool_config.heightlock)
|
||||
if plotnft.inner_puzzle.pool_config is not None
|
||||
else b"",
|
||||
bytes(plotnft.inner_puzzle.pool_config.pool_memoization)
|
||||
if plotnft.inner_puzzle.pool_config is not None
|
||||
else b"",
|
||||
plotnft.inner_puzzle.exiting,
|
||||
str(plotnft.remarks[0].rest.atom, "utf8")
|
||||
if len(plotnft.remarks) > 0 and plotnft.remarks[0].rest.atom is not None
|
||||
else None,
|
||||
|
||||
@@ -10,14 +10,15 @@ from chia_rs.sized_bytes import bytes32
|
||||
from chia_rs.sized_ints import uint8, uint32, uint64, uint128
|
||||
from typing_extensions import Self, Unpack
|
||||
|
||||
from chia.pools.plotnft_drivers import PlotNFT, PoolConfig, PoolReward, RewardPuzzle, SingletonStruct, UserConfig
|
||||
from chia.pools.plotnft_drivers import PlotNFT, PoolConfig, PoolReward, UserConfig
|
||||
from chia.pools.pool_config import PoolingShareState
|
||||
from chia.pools.pool_wallet_info import PoolSingletonState, PoolState, PoolWalletInfo
|
||||
from chia.server.ws_connection import WSChiaConnection
|
||||
from chia.types.blockchain_format.program import Program
|
||||
from chia.wallet.conditions import AssertCoinAnnouncement, Condition, CreateCoin, CreateCoinAnnouncement, Remark
|
||||
from chia.wallet.puzzles.custody.custody_architecture import DelegatedPuzzleAndSolution
|
||||
from chia.wallet.puzzles.p2_delegated_puzzle_or_hidden_puzzle import puzzle_hash_for_synthetic_public_key
|
||||
from chia.wallet.puzzles.puzzle_drivers import DelegatedPuzzleAndSolution, UnknownPuzzle, UnknownSolution
|
||||
from chia.wallet.puzzles.singleton_drivers import P2SingletonPuzzle, SingletonStruct
|
||||
from chia.wallet.util.wallet_types import WalletType
|
||||
from chia.wallet.wallet import Wallet
|
||||
from chia.wallet.wallet_action_scope import PlotNFTTargetStateInfo, WalletActionScope
|
||||
@@ -67,7 +68,7 @@ class PlotNFT2Wallet:
|
||||
|
||||
@property
|
||||
def hint(self) -> bytes32:
|
||||
return SingletonStruct(launcher_id=self.plotnft_id).struct_hash()
|
||||
return SingletonStruct(launcher_id=self.plotnft_id).struct_hash
|
||||
|
||||
@property
|
||||
def plotnft_id(self) -> bytes32:
|
||||
@@ -75,7 +76,7 @@ class PlotNFT2Wallet:
|
||||
|
||||
@property
|
||||
def p2_singleton_puzzle_hash(self) -> bytes32:
|
||||
return RewardPuzzle(singleton_id=self.plotnft_id).puzzle_hash()
|
||||
return P2SingletonPuzzle(singleton_id=self.plotnft_id).puzzle_hash
|
||||
|
||||
@property
|
||||
def rewards_claim_puzhash(self) -> bytes32:
|
||||
@@ -114,7 +115,7 @@ class PlotNFT2Wallet:
|
||||
target_puzzle_hash = await action_scope.get_puzzle_hash(wallet_state_manager)
|
||||
target_pubkey = G1Element.from_bytes(await wallet_state_manager.get_public_key(target_puzzle_hash))
|
||||
origin_coins = await xch_wallet.select_coins(amount=uint64(fee + 1), action_scope=action_scope)
|
||||
announcement_assertions, coin_spends, new_plotnft = PlotNFT.launch(
|
||||
launch_result = PlotNFT.launch_plotnft(
|
||||
origin_coins=list(origin_coins),
|
||||
user_config=UserConfig(synthetic_pubkey=xch_wallet.convert_public_key_to_synthetic(target_pubkey)),
|
||||
genesis_challenge=wallet_state_manager.constants.GENESIS_CHALLENGE,
|
||||
@@ -123,17 +124,19 @@ class PlotNFT2Wallet:
|
||||
remark=Remark(rest=Program.to(pool_url)) if pool_url is not None else None,
|
||||
)
|
||||
async with action_scope.use() as interface:
|
||||
interface.side_effects.extra_spends.append(WalletSpendBundle(coin_spends, G2Element()))
|
||||
interface.side_effects.extra_spends.append(WalletSpendBundle(launch_result.necessary_spends, G2Element()))
|
||||
create_launcher = next(cond for cond in launch_result.necessary_conditions if isinstance(cond, CreateCoin))
|
||||
other_conditions = iter(cond for cond in launch_result.necessary_conditions if not isinstance(cond, CreateCoin))
|
||||
await xch_wallet.generate_signed_transaction(
|
||||
amounts=[uint64(1)],
|
||||
puzzle_hashes=[new_plotnft.singleton_struct.singleton_puzzles.singleton_launcher_hash],
|
||||
amounts=[create_launcher.amount],
|
||||
puzzle_hashes=[create_launcher.puzzle_hash],
|
||||
action_scope=action_scope,
|
||||
fee=fee,
|
||||
coins=origin_coins,
|
||||
extra_conditions=(*announcement_assertions, *extra_conditions),
|
||||
origin_id=coin_spends[0].coin.parent_coin_info,
|
||||
extra_conditions=(*other_conditions, *extra_conditions),
|
||||
origin_id=launch_result.necessary_spends[0].coin.parent_coin_info,
|
||||
)
|
||||
return new_plotnft
|
||||
return launch_result.launched_singleton
|
||||
|
||||
async def claim_rewards(
|
||||
self,
|
||||
@@ -154,33 +157,37 @@ class PlotNFT2Wallet:
|
||||
rewards_to_claim=rewards_to_claim,
|
||||
reward_delegated_puzzles_and_solutions=[
|
||||
DelegatedPuzzleAndSolution(
|
||||
puzzle=self.xch_wallet.make_solution(
|
||||
primaries=[
|
||||
CreateCoin(
|
||||
puzzle_hash=self.rewards_claim_puzhash,
|
||||
amount=uint64(total_reward_amount - fee),
|
||||
),
|
||||
],
|
||||
fee=fee,
|
||||
conditions=(*extra_conditions, CreateCoinAnnouncement(b""))
|
||||
if len(rewards_to_claim) > 1
|
||||
else extra_conditions,
|
||||
).at("rf"), # strips away to just the delegated puzzle (bit of a hack)
|
||||
solution=Program.to(None),
|
||||
puzzle=UnknownPuzzle(
|
||||
known_puzzle=self.xch_wallet.make_solution(
|
||||
primaries=[
|
||||
CreateCoin(
|
||||
puzzle_hash=self.rewards_claim_puzhash,
|
||||
amount=uint64(total_reward_amount - fee),
|
||||
),
|
||||
],
|
||||
fee=fee,
|
||||
conditions=(*extra_conditions, CreateCoinAnnouncement(b""))
|
||||
if len(rewards_to_claim) > 1
|
||||
else extra_conditions,
|
||||
).at("rf")
|
||||
), # strips away to just the delegated puzzle (bit of a hack)
|
||||
solution=UnknownSolution(solution=Program.to(None)),
|
||||
)
|
||||
if i == 0
|
||||
else DelegatedPuzzleAndSolution(
|
||||
puzzle=Program.to(
|
||||
(
|
||||
1,
|
||||
[
|
||||
AssertCoinAnnouncement(
|
||||
asserted_id=rewards_to_claim[0].coin.name(), asserted_msg=b""
|
||||
).to_program()
|
||||
],
|
||||
puzzle=UnknownPuzzle(
|
||||
known_puzzle=Program.to(
|
||||
(
|
||||
1,
|
||||
[
|
||||
AssertCoinAnnouncement(
|
||||
asserted_id=rewards_to_claim[0].coin.name(), asserted_msg=b""
|
||||
).to_program()
|
||||
],
|
||||
)
|
||||
)
|
||||
),
|
||||
solution=Program.to(None),
|
||||
solution=UnknownSolution(solution=Program.to(None)),
|
||||
)
|
||||
for i, reward in enumerate(rewards_to_claim)
|
||||
],
|
||||
@@ -204,7 +211,7 @@ class PlotNFT2Wallet:
|
||||
),
|
||||
Coin(
|
||||
parent_coin_info=plotnft.coin.name(),
|
||||
puzzle_hash=plotnft.puzzle_hash(nonce=uint64(0)),
|
||||
puzzle_hash=plotnft.puzzle_hash,
|
||||
amount=uint64(1),
|
||||
),
|
||||
],
|
||||
@@ -229,7 +236,7 @@ class PlotNFT2Wallet:
|
||||
plotnft = await self.get_current_plotnft()
|
||||
else:
|
||||
plotnft = plotnft_override
|
||||
if plotnft.pool_config is not None:
|
||||
if plotnft.inner_puzzle.pool_config is not None:
|
||||
await self.leave_pool(
|
||||
action_scope=action_scope,
|
||||
fee=fee,
|
||||
@@ -244,7 +251,7 @@ class PlotNFT2Wallet:
|
||||
fee_hook = CreateCoinAnnouncement(msg=b"", coin_id=plotnft.coin.name())
|
||||
url_remark = Remark(rest=Program.to(pool_url))
|
||||
coin_spends = plotnft.join_pool(
|
||||
user_config=plotnft.user_config,
|
||||
user_config=plotnft.inner_puzzle.user_config,
|
||||
pool_config=pool_config,
|
||||
extra_conditions=(*extra_conditions, fee_hook, url_remark),
|
||||
)
|
||||
@@ -268,7 +275,9 @@ class PlotNFT2Wallet:
|
||||
additions=[
|
||||
Coin(
|
||||
parent_coin_info=plotnft.coin.name(),
|
||||
puzzle_hash=dataclasses.replace(plotnft, pool_config=pool_config).puzzle_hash(nonce=0),
|
||||
puzzle_hash=dataclasses.replace(
|
||||
plotnft, inner_puzzle=dataclasses.replace(plotnft.inner_puzzle, pool_config=pool_config)
|
||||
).puzzle_hash,
|
||||
amount=uint64(1),
|
||||
)
|
||||
],
|
||||
@@ -293,17 +302,21 @@ class PlotNFT2Wallet:
|
||||
):
|
||||
raise ValueError("Both new_pool_url or new_pool_config must be provided together")
|
||||
plotnft = await self.get_current_plotnft()
|
||||
if not plotnft.pooling or plotnft.exiting:
|
||||
if not plotnft.inner_puzzle.pooling or plotnft.inner_puzzle.exiting:
|
||||
raise ValueError("`leave_pool` called on a non-pooling or exiting PlotNFT")
|
||||
next_plotnft = dataclasses.replace(plotnft, exiting=True)
|
||||
next_plotnft = dataclasses.replace(
|
||||
plotnft, inner_puzzle=dataclasses.replace(plotnft.inner_puzzle, exiting=True)
|
||||
)
|
||||
fee_hook = CreateCoinAnnouncement(msg=b"", coin_id=plotnft.coin.name())
|
||||
exit_create_coin = plotnft.exit_to_waiting_room_condition()
|
||||
exit_create_coin = plotnft.inner_puzzle.exit_to_waiting_room_condition
|
||||
exit_to_waiting_room_dpuz_and_sol = DelegatedPuzzleAndSolution(
|
||||
puzzle=self.xch_wallet.make_solution(
|
||||
primaries=[exit_create_coin],
|
||||
conditions=(*extra_conditions, fee_hook),
|
||||
).at("rf"), # strips away to just the delegated puzzle (bit of a hack)
|
||||
solution=Program.to(None),
|
||||
puzzle=UnknownPuzzle(
|
||||
known_puzzle=self.xch_wallet.make_solution(
|
||||
primaries=[exit_create_coin],
|
||||
conditions=(*extra_conditions, fee_hook),
|
||||
).at("rf")
|
||||
), # strips away to just the delegated puzzle (bit of a hack)
|
||||
solution=UnknownSolution(solution=Program.to(None)),
|
||||
)
|
||||
coin_spends = plotnft.exit_to_waiting_room(exit_to_waiting_room_dpuz_and_sol)
|
||||
if fee > 0:
|
||||
@@ -334,7 +347,7 @@ class PlotNFT2Wallet:
|
||||
additions=[
|
||||
Coin(
|
||||
parent_coin_info=plotnft.coin.name(),
|
||||
puzzle_hash=next_plotnft.puzzle_hash(nonce=0),
|
||||
puzzle_hash=next_plotnft.puzzle_hash,
|
||||
amount=uint64(1),
|
||||
)
|
||||
],
|
||||
@@ -353,19 +366,21 @@ class PlotNFT2Wallet:
|
||||
) -> None:
|
||||
plotnft = await self.get_current_plotnft()
|
||||
fee_hook = CreateCoinAnnouncement(msg=b"", coin_id=plotnft.coin.name())
|
||||
heightlock, exit_create_coin = plotnft.exit_from_waiting_room_conditions()
|
||||
heightlock, exit_create_coin = plotnft.inner_puzzle.exit_from_waiting_room_conditions
|
||||
exit_to_waiting_room_dpuz_and_sol = DelegatedPuzzleAndSolution(
|
||||
puzzle=self.xch_wallet.make_solution(
|
||||
primaries=[exit_create_coin],
|
||||
conditions=(fee_hook, heightlock, *extra_conditions),
|
||||
).at("rf"), # strips away to just the delegated puzzle (bit of a hack)
|
||||
solution=Program.to(None),
|
||||
puzzle=UnknownPuzzle(
|
||||
known_puzzle=self.xch_wallet.make_solution(
|
||||
primaries=[exit_create_coin],
|
||||
conditions=(fee_hook, heightlock, *extra_conditions),
|
||||
).at("rf")
|
||||
), # strips away to just the delegated puzzle (bit of a hack)
|
||||
solution=UnknownSolution(solution=Program.to(None)),
|
||||
)
|
||||
coin_spends = plotnft.exit_waiting_room(exit_to_waiting_room_dpuz_and_sol)
|
||||
next_plotnft = PlotNFT.get_next_from_coin_spend(
|
||||
coin_spend=coin_spends[0],
|
||||
genesis_challenge=self.wallet_state_manager.constants.GENESIS_CHALLENGE,
|
||||
previous_plotnft_puzzle=plotnft,
|
||||
previous_plotnft_puzzle=plotnft.inner_puzzle,
|
||||
)
|
||||
if exiting_info.exiting_fee > 0:
|
||||
await self.xch_wallet.create_tandem_xch_tx(
|
||||
@@ -387,9 +402,10 @@ class PlotNFT2Wallet:
|
||||
additions=[
|
||||
Coin(
|
||||
parent_coin_info=plotnft.coin.name(),
|
||||
puzzle_hash=dataclasses.replace(plotnft, pool_config=None, exiting=False).puzzle_hash(
|
||||
nonce=0
|
||||
),
|
||||
puzzle_hash=dataclasses.replace(
|
||||
plotnft,
|
||||
inner_puzzle=dataclasses.replace(plotnft.inner_puzzle, pool_config=None, exiting=False),
|
||||
).puzzle_hash,
|
||||
amount=uint64(1),
|
||||
)
|
||||
],
|
||||
@@ -411,7 +427,7 @@ class PlotNFT2Wallet:
|
||||
async def coin_added(self, coin: Coin, height: uint32, peer: WSChiaConnection, coin_data: object | None) -> None:
|
||||
if isinstance(coin_data, PlotNFT):
|
||||
index = await self.wallet_state_manager.puzzle_store.index_for_puzzle_hash(
|
||||
puzzle_hash_for_synthetic_public_key(coin_data.user_config.synthetic_pubkey)
|
||||
puzzle_hash_for_synthetic_public_key(coin_data.inner_puzzle.user_config.synthetic_pubkey)
|
||||
)
|
||||
if index is None:
|
||||
raise ValueError(f"No index found for synthetic pubkey for launcher_id: {coin_data.launcher_id}")
|
||||
@@ -424,14 +440,14 @@ class PlotNFT2Wallet:
|
||||
root_path=self.wallet_state_manager.root_path,
|
||||
p2_singleton_puzzle_hash=self.p2_singleton_puzzle_hash,
|
||||
) as pool_config:
|
||||
pool_config.owner_public_key = coin_data.user_config.synthetic_pubkey
|
||||
pool_config.owner_public_key = coin_data.inner_puzzle.user_config.synthetic_pubkey
|
||||
pool_config.key_derivation_index = int(index)
|
||||
if coin_data.pool_config is not None:
|
||||
if coin_data.pool_config.pool_puzzle_hash != pool_config.target_puzzle_hash:
|
||||
if coin_data.inner_puzzle.pool_config is not None:
|
||||
if coin_data.inner_puzzle.pool_config.pool_puzzle_hash != pool_config.target_puzzle_hash:
|
||||
pool_config.pool_url = await self.wallet_state_manager.plotnft2_store.get_latest_remark(
|
||||
coin_data.launcher_id
|
||||
)
|
||||
pool_config.target_puzzle_hash = coin_data.pool_config.pool_puzzle_hash
|
||||
pool_config.target_puzzle_hash = coin_data.inner_puzzle.pool_config.pool_puzzle_hash
|
||||
else:
|
||||
pool_config.target_puzzle_hash = bytes32.from_hexstr(pool_config.payout_instructions)
|
||||
else:
|
||||
@@ -442,11 +458,11 @@ class PlotNFT2Wallet:
|
||||
PoolingShareState(
|
||||
launcher_id=coin_data.launcher_id,
|
||||
pool_url=await self.wallet_state_manager.plotnft2_store.get_latest_remark(coin_data.launcher_id)
|
||||
if coin_data.pool_config is not None
|
||||
if coin_data.inner_puzzle.pool_config is not None
|
||||
else "",
|
||||
owner_public_key=coin_data.user_config.synthetic_pubkey,
|
||||
target_puzzle_hash=coin_data.pool_config.pool_puzzle_hash
|
||||
if coin_data.pool_config is not None
|
||||
owner_public_key=coin_data.inner_puzzle.user_config.synthetic_pubkey,
|
||||
target_puzzle_hash=coin_data.inner_puzzle.pool_config.pool_puzzle_hash
|
||||
if coin_data.inner_puzzle.pool_config is not None
|
||||
else payout_puzzle_hash,
|
||||
p2_singleton_puzzle_hash=self.p2_singleton_puzzle_hash,
|
||||
payout_instructions=payout_puzzle_hash.hex(),
|
||||
@@ -454,9 +470,10 @@ class PlotNFT2Wallet:
|
||||
version=2,
|
||||
).add(root_path=self.wallet_state_manager.root_path)
|
||||
|
||||
if coin_data.exiting:
|
||||
if coin_data.inner_puzzle.exiting:
|
||||
await self.wallet_state_manager.plotnft2_store.add_exiting_height(
|
||||
wallet_id=self.id(), height=uint32(height + coin_data.guaranteed_pool_config.heightlock)
|
||||
wallet_id=self.id(),
|
||||
height=uint32(height + coin_data.inner_puzzle.guaranteed_pool_config.heightlock),
|
||||
)
|
||||
else:
|
||||
finish_height = await self.wallet_state_manager.plotnft2_store.get_exiting_height(wallet_id=self.id())
|
||||
@@ -516,12 +533,12 @@ class PlotNFT2Wallet:
|
||||
self,
|
||||
) -> PoolWalletInfo: # backwards compat with previous pool wallet
|
||||
plotnft = await self.get_current_plotnft()
|
||||
if plotnft.pool_config is None:
|
||||
if plotnft.inner_puzzle.pool_config is None:
|
||||
singleton_state = PoolSingletonState.SELF_POOLING
|
||||
rewards_claim_ph = self.rewards_claim_puzhash
|
||||
else:
|
||||
rewards_claim_ph = plotnft.pool_config.pool_puzzle_hash
|
||||
if plotnft.exiting:
|
||||
rewards_claim_ph = plotnft.inner_puzzle.pool_config.pool_puzzle_hash
|
||||
if plotnft.inner_puzzle.exiting:
|
||||
singleton_state = PoolSingletonState.LEAVING_POOL
|
||||
else:
|
||||
singleton_state = PoolSingletonState.FARMING_TO_POOL
|
||||
@@ -531,11 +548,13 @@ class PlotNFT2Wallet:
|
||||
version=uint8(2),
|
||||
state=uint8(singleton_state.value),
|
||||
target_puzzle_hash=rewards_claim_ph,
|
||||
owner_pubkey=plotnft.user_config.synthetic_pubkey,
|
||||
owner_pubkey=plotnft.inner_puzzle.user_config.synthetic_pubkey,
|
||||
pool_url=await self.wallet_state_manager.plotnft2_store.get_latest_remark(plotnft.launcher_id)
|
||||
if plotnft.pool_config is not None
|
||||
if plotnft.inner_puzzle.pool_config is not None
|
||||
else None,
|
||||
relative_lock_height=plotnft.pool_config.heightlock if plotnft.pool_config is not None else uint32(0),
|
||||
relative_lock_height=plotnft.inner_puzzle.pool_config.heightlock
|
||||
if plotnft.inner_puzzle.pool_config is not None
|
||||
else uint32(0),
|
||||
),
|
||||
target=PoolState(
|
||||
version=uint8(2),
|
||||
@@ -545,17 +564,17 @@ class PlotNFT2Wallet:
|
||||
target_puzzle_hash=self.rewards_claim_puzhash
|
||||
if exiting_info.next_pool_puzzle_hash is None
|
||||
else exiting_info.next_pool_puzzle_hash,
|
||||
owner_pubkey=plotnft.user_config.synthetic_pubkey,
|
||||
owner_pubkey=plotnft.inner_puzzle.user_config.synthetic_pubkey,
|
||||
pool_url=None if exiting_info.next_pool_url is None else exiting_info.next_pool_url,
|
||||
relative_lock_height=uint32(0)
|
||||
if exiting_info.next_heightlock is None
|
||||
else exiting_info.next_heightlock,
|
||||
)
|
||||
if plotnft.exiting
|
||||
if plotnft.inner_puzzle.exiting
|
||||
else None,
|
||||
launcher_coin=Coin(bytes32.zeros, bytes32.zeros, uint64(0)),
|
||||
launcher_id=plotnft.launcher_id,
|
||||
p2_singleton_puzzle_hash=RewardPuzzle(singleton_id=plotnft.launcher_id).puzzle_hash(),
|
||||
p2_singleton_puzzle_hash=P2SingletonPuzzle(singleton_id=plotnft.launcher_id).puzzle_hash,
|
||||
tip_singleton_coin_id=plotnft.coin.name(),
|
||||
singleton_block_height=await self.wallet_state_manager.plotnft2_store.get_plotnft_created_height(
|
||||
coin_id=plotnft.coin.name()
|
||||
|
||||
@@ -9,7 +9,7 @@ from chia_rs.sized_bytes import bytes32
|
||||
from typing_extensions import runtime_checkable
|
||||
|
||||
from chia.types.blockchain_format.program import Program
|
||||
from chia.wallet.puzzles.puzzle_drivers import InnerPuzzle, UnknownPuzzle
|
||||
from chia.wallet.puzzles.puzzle_drivers import UnknownPuzzle
|
||||
from chia.wallet.util.merkle_tree import MerkleTree, hash_a_pair, hash_an_atom
|
||||
|
||||
MofN_MOD = Program.from_bytes(puzzle_mods.M_OF_N)
|
||||
@@ -217,6 +217,27 @@ class MofN: # Technically matches Puzzle protocol but is a bespoke part of the
|
||||
else:
|
||||
return self.puzzle(nonce).get_tree_hash()
|
||||
|
||||
@classmethod
|
||||
def match(cls, *, unknown_puzzle: UnknownPuzzle, solution: object | None = None) -> MofN | None:
|
||||
if unknown_puzzle.mod not in [MofN_MOD, NofN_MOD, OneOfN_MOD] or unknown_puzzle.curried_args is None: # ruff: ignore[literal-membership]
|
||||
return None
|
||||
|
||||
if unknown_puzzle.mod == NofN_MOD:
|
||||
list_of_members = [_ for _ in unknown_puzzle.curried_args]
|
||||
pwr_matches = [
|
||||
PuzzleWithRestrictions.match(unknown_puzzle=UnknownPuzzle(known_puzzle=member))
|
||||
for member in list_of_members
|
||||
]
|
||||
if None in pwr_matches:
|
||||
return None
|
||||
# come on mypy, be better
|
||||
return MofN(m=len(list_of_members), members=pwr_matches) # type: ignore[arg-type]
|
||||
elif unknown_puzzle.mod == MofN_MOD:
|
||||
(m, _) = unknown_puzzle.curried_args
|
||||
return MofN(m=m.as_int(), members=[])
|
||||
else:
|
||||
return MofN(m=1, members=[])
|
||||
|
||||
|
||||
# A convenience object for hinting the two solution values that must always exist
|
||||
@dataclass(kw_only=True, frozen=True)
|
||||
@@ -432,7 +453,7 @@ class PuzzleWithRestrictions:
|
||||
return solution
|
||||
|
||||
@classmethod
|
||||
def match(cls, *, unknown_puzzle: UnknownPuzzle, solution: object | None = None) -> InnerPuzzle | None:
|
||||
def match(cls, *, unknown_puzzle: UnknownPuzzle, solution: object | None = None) -> PuzzleWithRestrictions | None:
|
||||
if unknown_puzzle.mod != INDEX_WRAPPER or unknown_puzzle.curried_args is None:
|
||||
return None
|
||||
|
||||
@@ -465,4 +486,4 @@ class PuzzleWithRestrictions:
|
||||
restrictions = []
|
||||
inner_puzzle = potentially_restricted_puzzle
|
||||
|
||||
return cls(nonce=nonce, restrictions=restrictions, puzzle=inner_puzzle) # type: ignore[return-value, arg-type]
|
||||
return cls(nonce=nonce, restrictions=restrictions, puzzle=inner_puzzle) # type: ignore[arg-type]
|
||||
|
||||
@@ -11,6 +11,7 @@ from chia.wallet.puzzles.p2_delegated_puzzle_or_hidden_puzzle import (
|
||||
calculate_synthetic_public_key,
|
||||
calculate_synthetic_secret_key,
|
||||
)
|
||||
from chia.wallet.puzzles.puzzle_drivers import UnknownPuzzle, UnknownSolution
|
||||
from chia.wallet.singleton import SINGLETON_LAUNCHER_PUZZLE_HASH, SINGLETON_TOP_LAYER_MOD_HASH
|
||||
|
||||
BLS_WITH_TAPROOT_MEMBER_MOD = Program.from_bytes(puzzle_mods.BLS_WITH_TAPROOT_MEMBER)
|
||||
@@ -33,13 +34,16 @@ class BLSWithTaprootMember:
|
||||
def memo(self, nonce: int) -> Program:
|
||||
return Program.to(0)
|
||||
|
||||
def puzzle(self, nonce: int) -> Program:
|
||||
if self.synthetic_key is None:
|
||||
assert self.public_key is not None and self.hidden_puzzle is not None
|
||||
synthetic_public_key = calculate_synthetic_public_key(self.public_key, self.hidden_puzzle.get_tree_hash())
|
||||
return BLS_WITH_TAPROOT_MEMBER_MOD.curry(bytes(synthetic_public_key))
|
||||
@property
|
||||
def guaranteed_synthetic_key(self) -> G1Element:
|
||||
if self.synthetic_key is not None:
|
||||
return self.synthetic_key
|
||||
else:
|
||||
return BLS_WITH_TAPROOT_MEMBER_MOD.curry(bytes(self.synthetic_key))
|
||||
assert self.hidden_puzzle is not None # guarded by __post_init__
|
||||
return calculate_synthetic_public_key(self.public_key, self.hidden_puzzle.get_tree_hash())
|
||||
|
||||
def puzzle(self, nonce: int) -> Program:
|
||||
return BLS_WITH_TAPROOT_MEMBER_MOD.curry(bytes(self.guaranteed_synthetic_key))
|
||||
|
||||
def puzzle_hash(self, nonce: int) -> bytes32:
|
||||
return self.puzzle(nonce).get_tree_hash()
|
||||
@@ -57,6 +61,49 @@ class BLSWithTaprootMember:
|
||||
return Program.to([self.public_key, self.hidden_puzzle])
|
||||
return Program.to([0])
|
||||
|
||||
@classmethod
|
||||
def match(
|
||||
cls, *, unknown_puzzle: UnknownPuzzle, unknown_solution: UnknownSolution | None = None
|
||||
) -> BLSWithTaprootMember | None:
|
||||
if unknown_puzzle.mod != BLS_WITH_TAPROOT_MEMBER_MOD or unknown_puzzle.curried_args is None:
|
||||
return None
|
||||
(synthetic_key_prog,) = unknown_puzzle.curried_args
|
||||
synthetic_key = G1Element.from_bytes(synthetic_key_prog.as_atom())
|
||||
original_key = None
|
||||
hidden_puzzle = None
|
||||
if unknown_solution is not None:
|
||||
solution_match = BLSWithTaprootMemberSolution.match(unknown_solution=unknown_solution)
|
||||
if solution_match is not None:
|
||||
original_key = solution_match.original_public_key
|
||||
hidden_puzzle = solution_match.hidden_puzzle
|
||||
return BLSWithTaprootMember(synthetic_key=synthetic_key, public_key=original_key, hidden_puzzle=hidden_puzzle)
|
||||
|
||||
|
||||
@dataclass(kw_only=True, frozen=True)
|
||||
class BLSWithTaprootMemberSolution:
|
||||
original_public_key: G1Element | None = None
|
||||
hidden_puzzle: Program | None = None
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
if (self.original_public_key is not None and self.hidden_puzzle is None) or (
|
||||
self.original_public_key is None and self.hidden_puzzle is not None
|
||||
):
|
||||
raise ValueError("Must specify both or neither of original_public_key and hidden_puzzle")
|
||||
|
||||
@classmethod
|
||||
def match(cls, *, unknown_solution: UnknownSolution) -> BLSWithTaprootMemberSolution | None:
|
||||
if unknown_solution.as_program().atom is not None:
|
||||
return None
|
||||
list_of_values = list(unknown_solution.as_program().as_iter())
|
||||
if len(list_of_values) == 2:
|
||||
return BLSWithTaprootMemberSolution(
|
||||
original_public_key=G1Element.from_bytes(list_of_values[0].as_atom()), hidden_puzzle=list_of_values[1]
|
||||
)
|
||||
elif len(list_of_values) == 1:
|
||||
return BLSWithTaprootMemberSolution()
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
@dataclass(kw_only=True, frozen=True)
|
||||
class SingletonMember:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Iterator
|
||||
from dataclasses import dataclass, field
|
||||
from dataclasses import dataclass
|
||||
from functools import cached_property
|
||||
from typing import TYPE_CHECKING, ClassVar, Protocol, TypeVar, cast
|
||||
|
||||
@@ -50,25 +50,25 @@ class OptimizedPuzzleHashPuzzle(Protocol):
|
||||
def puzzle_hash_optimized(self) -> bytes32: ...
|
||||
|
||||
|
||||
@dataclass
|
||||
class PuzzleWithPuzzleHash:
|
||||
"""
|
||||
This is designed to be a base class to `Inner/OuterPuzzle`s which provides caching on the puzzle hash generation
|
||||
"""
|
||||
|
||||
pre_computed_puzzle_hash: bytes32 | None = field(default=None, kw_only=True)
|
||||
pre_computed_puzzle_hash: bytes32 | None = None
|
||||
|
||||
@property
|
||||
def puzzle_hash(self) -> bytes32:
|
||||
if self.pre_computed_puzzle_hash is None:
|
||||
if isinstance(self, OptimizedPuzzleHashPuzzle):
|
||||
self.pre_computed_puzzle_hash = self.puzzle_hash_optimized
|
||||
object.__setattr__(self, "pre_computed_puzzle_hash", self.puzzle_hash_optimized)
|
||||
else:
|
||||
self.pre_computed_puzzle_hash = self.puzzle.get_tree_hash() # type: ignore[attr-defined]
|
||||
object.__setattr__(self, "pre_computed_puzzle_hash", self.puzzle.get_tree_hash()) # type: ignore[attr-defined]
|
||||
assert self.pre_computed_puzzle_hash is not None
|
||||
return self.pre_computed_puzzle_hash
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
@dataclass(kw_only=True, frozen=True)
|
||||
class UnknownPuzzle(PuzzleWithPuzzleHash):
|
||||
if TYPE_CHECKING:
|
||||
_protocol_check: ClassVar[InnerPuzzle] = cast("UnknownPuzzle", None)
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from collections.abc import Sequence
|
||||
from dataclasses import dataclass, field, replace
|
||||
from functools import cached_property
|
||||
from typing import TYPE_CHECKING, ClassVar, Generic, TypeVar, cast
|
||||
from typing import TYPE_CHECKING, Any, ClassVar, Generic, TypeVar, cast
|
||||
|
||||
from chia_rs import Coin, CoinSpend
|
||||
from chia_rs.sized_bytes import bytes32
|
||||
@@ -95,7 +96,7 @@ class SingletonStruct:
|
||||
_T_InnerPuzzle = TypeVar("_T_InnerPuzzle", bound=InnerPuzzle)
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
@dataclass(kw_only=True, frozen=True)
|
||||
class SingletonPuzzle(PuzzleWithPuzzleHash, Generic[_T_InnerPuzzle]):
|
||||
if TYPE_CHECKING:
|
||||
_outer_puzzle_protocol_check: ClassVar[OuterPuzzle[InnerPuzzle]] = cast("SingletonPuzzle[_T_InnerPuzzle]", None)
|
||||
@@ -140,6 +141,11 @@ class SingletonPuzzle(PuzzleWithPuzzleHash, Generic[_T_InnerPuzzle]):
|
||||
inner_puzzle=UnknownPuzzle(known_puzzle=inner_puzzle),
|
||||
)
|
||||
|
||||
def with_inner_puzzle(self, inner_puzzle: InnerPuzzle) -> Any:
|
||||
# should be used as little as possible because it doesn't seem possible to return a
|
||||
# newly paramed instance of the current type so we have to resort to Any
|
||||
return replace(self, inner_puzzle=inner_puzzle) # type: ignore[arg-type]
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class SingletonSolution:
|
||||
@@ -187,7 +193,7 @@ class SingletonLaunchInfo(Generic[_T_InnerPuzzle]):
|
||||
@dataclass(kw_only=True, frozen=True)
|
||||
class SingletonLaunchResult(Generic[_T_InnerPuzzle]):
|
||||
necessary_conditions: list[Condition]
|
||||
launcher_spend: CoinSpend
|
||||
necessary_spends: list[CoinSpend]
|
||||
launched_singleton: Singleton[_T_InnerPuzzle]
|
||||
|
||||
|
||||
@@ -199,10 +205,7 @@ def _new_create_coin_from_inner_puzzle_and_solution(inner_puzzle: InnerPuzzle, s
|
||||
)
|
||||
|
||||
|
||||
_T_LaunchInnerPuzzle = TypeVar("_T_LaunchInnerPuzzle", bound=InnerPuzzle)
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
@dataclass(kw_only=True, frozen=True)
|
||||
class Singleton(SingletonPuzzle[_T_InnerPuzzle]):
|
||||
if TYPE_CHECKING:
|
||||
_smart_coin_protocol_check: ClassVar[SmartCoin] = cast("Singleton[_T_InnerPuzzle]", None)
|
||||
@@ -210,6 +213,8 @@ class Singleton(SingletonPuzzle[_T_InnerPuzzle]):
|
||||
coin: Coin
|
||||
lineage_proof: LineageProof
|
||||
|
||||
_T_LaunchInnerPuzzle = TypeVar("_T_LaunchInnerPuzzle", bound=InnerPuzzle)
|
||||
|
||||
@classmethod
|
||||
def launch(
|
||||
cls,
|
||||
@@ -240,11 +245,13 @@ class Singleton(SingletonPuzzle[_T_InnerPuzzle]):
|
||||
|
||||
return SingletonLaunchResult(
|
||||
necessary_conditions=[create_launcher_condition, assert_launcher_announcement],
|
||||
launcher_spend=make_spend(
|
||||
launcher_coin,
|
||||
cls.singleton_puzzles.singleton_launcher,
|
||||
launcher_solution,
|
||||
),
|
||||
necessary_spends=[
|
||||
make_spend(
|
||||
launcher_coin,
|
||||
cls.singleton_puzzles.singleton_launcher,
|
||||
launcher_solution,
|
||||
)
|
||||
],
|
||||
launched_singleton=Singleton(
|
||||
coin=Coin(
|
||||
parent_coin_info=launcher_id,
|
||||
@@ -287,7 +294,7 @@ class Singleton(SingletonPuzzle[_T_InnerPuzzle]):
|
||||
def claim_p2_singletons(
|
||||
self,
|
||||
*,
|
||||
rewards_to_claim: list[P2Singleton],
|
||||
rewards_to_claim: Sequence[P2Singleton],
|
||||
reward_delegated_puzzles_and_solutions: list[DelegatedPuzzleAndSolution],
|
||||
) -> tuple[list[CoinSpend], list[SendMessage]]:
|
||||
if len(rewards_to_claim) != len(reward_delegated_puzzles_and_solutions):
|
||||
@@ -313,7 +320,7 @@ class Singleton(SingletonPuzzle[_T_InnerPuzzle]):
|
||||
], messages_to_send
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
@dataclass(kw_only=True, frozen=True)
|
||||
class P2SingletonPuzzle(PuzzleWithPuzzleHash):
|
||||
if TYPE_CHECKING:
|
||||
_protocol_check: ClassVar[InnerPuzzle] = cast("P2SingletonPuzzle", None)
|
||||
@@ -359,6 +366,6 @@ class P2SingletonPuzzle(PuzzleWithPuzzleHash):
|
||||
return cls(singleton_id=bytes32(singleton_struct_prog.at("rf").as_atom()))
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
@dataclass(kw_only=True, frozen=True)
|
||||
class P2Singleton(P2SingletonPuzzle):
|
||||
coin: Coin
|
||||
|
||||
@@ -28,13 +28,13 @@ from chia.wallet.puzzles.puzzle_drivers import (
|
||||
from chia.wallet.util.curry_and_treehash import curry_and_treehash, shatree_atom
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
@dataclass(kw_only=True, frozen=True)
|
||||
class HiddenPuzzleInfo(PuzzleWithPuzzleHash):
|
||||
puzzle: Program = field(default_factory=lambda: DEFAULT_HIDDEN_PUZZLE)
|
||||
pre_computed_puzzle_hash: bytes32 | None = field(default=DEFAULT_HIDDEN_PUZZLE_HASH, kw_only=True)
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
@dataclass(kw_only=True, frozen=True)
|
||||
class StandardPuzzle(PuzzleWithPuzzleHash):
|
||||
if TYPE_CHECKING:
|
||||
_protocol_check: ClassVar[InnerPuzzle] = cast("StandardPuzzle", None)
|
||||
@@ -51,9 +51,12 @@ class StandardPuzzle(PuzzleWithPuzzleHash):
|
||||
def synthetic_public_key(self) -> G1Element:
|
||||
if self.pre_known_synthetic_public_key is None:
|
||||
assert self.pre_known_original_public_key is not None # guarded by __post_init__
|
||||
self.pre_known_synthetic_public_key = calculate_synthetic_public_key(
|
||||
self.pre_known_original_public_key, self.hidden_puzzle_info.puzzle_hash
|
||||
object.__setattr__(
|
||||
self,
|
||||
"pre_known_synthetic_public_key",
|
||||
calculate_synthetic_public_key(self.pre_known_original_public_key, self.hidden_puzzle_info.puzzle_hash),
|
||||
)
|
||||
assert self.pre_known_synthetic_public_key is not None
|
||||
return self.pre_known_synthetic_public_key
|
||||
|
||||
@property
|
||||
@@ -80,8 +83,9 @@ class StandardPuzzle(PuzzleWithPuzzleHash):
|
||||
raise ValueError("Trying to match a standard puzzle without a standard puzzle solution")
|
||||
if solution.original_public_key is not None:
|
||||
original_public_key = solution.original_public_key
|
||||
hidden_puzzle_info.puzzle = solution.delegated_puzzle
|
||||
hidden_puzzle_info.pre_computed_puzzle_hash = None
|
||||
hidden_puzzle_info = HiddenPuzzleInfo(
|
||||
puzzle=solution.delegated_puzzle, pre_computed_puzzle_hash=None
|
||||
)
|
||||
return cls(
|
||||
pre_known_synthetic_public_key=G1Element.from_bytes(list_of_args[0].as_atom()),
|
||||
pre_known_original_public_key=original_public_key,
|
||||
@@ -136,7 +140,7 @@ class StandardPuzzleSolution:
|
||||
)
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
@dataclass(kw_only=True, frozen=True)
|
||||
class StandardXCHCoin(StandardPuzzle):
|
||||
if TYPE_CHECKING:
|
||||
_protocol_check_2: ClassVar[SmartCoin] = cast("StandardXCHCoin", None)
|
||||
|
||||
@@ -18,7 +18,7 @@ from clvm_tools.binutils import assemble
|
||||
from chia.consensus.block_rewards import calculate_base_farmer_reward
|
||||
from chia.data_layer.data_layer_util import DLProof, VerifyProofResponse, dl_verify_proof
|
||||
from chia.data_layer.data_layer_wallet import Mirror
|
||||
from chia.pools.plotnft_drivers import PoolConfig, RewardPuzzle
|
||||
from chia.pools.plotnft_drivers import PoolConfig
|
||||
from chia.pools.pool_wallet import PoolWallet
|
||||
from chia.pools.pool_wallet_info import (
|
||||
FARMING_TO_POOL,
|
||||
@@ -64,6 +64,7 @@ from chia.wallet.outer_puzzles import AssetType
|
||||
from chia.wallet.plotnft_wallet.plotnft_wallet import PlotNFT2Wallet
|
||||
from chia.wallet.puzzle_drivers import PuzzleInfo
|
||||
from chia.wallet.puzzles.clawback.metadata import AutoClaimSettings, ClawbackMetadata
|
||||
from chia.wallet.puzzles.singleton_drivers import P2SingletonPuzzle
|
||||
from chia.wallet.remote_wallet.remote_wallet import RemoteWallet
|
||||
from chia.wallet.signer_protocol import SigningResponse
|
||||
from chia.wallet.singleton import (
|
||||
@@ -1214,7 +1215,7 @@ class WalletRpcApi:
|
||||
transaction=REPLACEABLE_TRANSACTION_RECORD,
|
||||
total_fee=uint64(request.fee),
|
||||
launcher_id=plotnft.launcher_id,
|
||||
p2_singleton_puzzle_hash=RewardPuzzle(singleton_id=plotnft.launcher_id).puzzle_hash(),
|
||||
p2_singleton_puzzle_hash=P2SingletonPuzzle(singleton_id=plotnft.launcher_id).puzzle_hash,
|
||||
# irrelevant, will be replace in serialization
|
||||
type=WalletType.PLOTNFT_2.name,
|
||||
wallet_id=uint32(0),
|
||||
|
||||
@@ -1002,7 +1002,7 @@ class WalletStateManager:
|
||||
coin_spend=coin_spend,
|
||||
genesis_challenge=self.constants.GENESIS_CHALLENGE,
|
||||
pre_uncurry=uncurried,
|
||||
previous_plotnft_puzzle=previous_plotnft,
|
||||
previous_plotnft_puzzle=previous_plotnft.inner_puzzle if previous_plotnft is not None else None,
|
||||
)
|
||||
for id, wallet in self.wallets.items():
|
||||
if isinstance(wallet, PlotNFT2Wallet) and wallet.plotnft_id == next_plot_nft.launcher_id:
|
||||
|
||||
Reference in New Issue
Block a user