Skip to content

Commit 4448bce

Browse files
committed
Add a first crack at syncing
1 parent 3761d11 commit 4448bce

File tree

6 files changed

+154
-36
lines changed

6 files changed

+154
-36
lines changed

chia/_tests/pools/test_plotnft_v2_drivers.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ async def mint_plotnft(
6565
else:
6666
custody = self_custody
6767

68-
conditions, spends, plotnft = PlotNFT.launch(origin_coins=[origin_coin], custody=custody, memos=[])
68+
conditions, spends, plotnft = PlotNFT.launch(origin_coins=[origin_coin], custody=custody)
6969

7070
result = await sim_client.push_tx(
7171
WalletSpendBundle(
@@ -75,6 +75,10 @@ async def mint_plotnft(
7575
)
7676
assert result == (MempoolInclusionStatus.SUCCESS, None)
7777
await sim.farm_block()
78+
assert (
79+
PlotNFT.get_next_from_coin_spend(coin_spend=spends[1], genesis_challenge=sim.defaults.GENESIS_CHALLENGE)
80+
== plotnft
81+
)
7882
return plotnft
7983

8084

chia/data_layer/data_layer_wallet.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1058,7 +1058,9 @@ async def make_update_offer(
10581058

10591059
# create some dummy requested payments
10601060
requested_payments = {
1061-
k: [NotarizedPayment(bytes32.zeros, uint64(v), [], bytes32.zeros)] for k, v in offer_dict.items() if v > 0
1061+
k: [NotarizedPayment(bytes32.zeros, uint64(v), [], nonce=bytes32.zeros)]
1062+
for k, v in offer_dict.items()
1063+
if v > 0
10621064
}
10631065

10641066
async with action_scope.use() as interface:

chia/pools/plotnft_drivers.py

Lines changed: 113 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,14 @@
22

33
import dataclasses
44
from dataclasses import dataclass, field
5-
from typing import Self
5+
from typing import ClassVar, Self
66

7+
from chia_rs import G1Element
78
from chia_rs.chia_rs import Coin, CoinSpend
89
from chia_rs.sized_bytes import bytes32
910
from chia_rs.sized_ints import uint32, uint64
1011

11-
from chia.types.blockchain_format.program import Program
12+
from chia.types.blockchain_format.program import Program, run
1213
from chia.types.coin_spend import make_spend
1314
from chia.wallet.conditions import (
1415
AssertCoinAnnouncement,
@@ -18,6 +19,7 @@
1819
MessageParticipant,
1920
ReserveFee,
2021
SendMessage,
22+
parse_conditions_non_consensus,
2123
)
2224
from chia.wallet.lineage_proof import LineageProof
2325
from chia.wallet.puzzles.custody.custody_architecture import (
@@ -36,6 +38,7 @@
3638
puzzle_for_singleton,
3739
solution_for_singleton,
3840
)
41+
from chia.wallet.uncurried_puzzle import UncurriedPuzzle, uncurry_puzzle
3942

4043
CLAIM_POOL_REWARDS_DELEGATED_PUZZLE = load_clvm_maybe_recompile(
4144
"claim_pool_rewards_dpuz.clsp", package_or_requirement="chia.pools"
@@ -83,11 +86,12 @@ class SelfCustody:
8386

8487
def puzzle_with_restrictions(self) -> PuzzleWithRestrictions:
8588
return PuzzleWithRestrictions(
86-
nonce=0,
87-
restrictions=[],
88-
puzzle=self.member,
89+
nonce=0, restrictions=[], puzzle=self.member, additional_memos=self.memos(nonce=0)
8990
)
9091

92+
def memos(self, nonce: int) -> Program:
93+
return Program.to([self.member.synthetic_key])
94+
9195
def puzzle(self, nonce: int) -> Program:
9296
return self.puzzle_with_restrictions().puzzle_reveal()
9397

@@ -199,8 +203,12 @@ def puzzle_with_restrictions(self) -> PuzzleWithRestrictions:
199203
self.pool_puzzle_with_restrictions(),
200204
],
201205
),
206+
additional_memos=self.memos(nonce=0),
202207
)
203208

209+
def memos(self, nonce: int) -> Program:
210+
return Program.to([self.self_custody.member.synthetic_key, self.pool_puzzle_hash, self.timelock])
211+
204212
def puzzle(self, nonce: int) -> Program:
205213
return self.puzzle_with_restrictions().puzzle_reveal()
206214

@@ -267,20 +275,21 @@ class PlotNFT:
267275
coin: Coin
268276
singleton_lineage_proof: LineageProof
269277
puzzle: PlotNFTPuzzle
278+
singleton_mod: ClassVar[Program] = SINGLETON_MOD
279+
singleton_launcher: ClassVar[Program] = SINGLETON_LAUNCHER
270280

271-
@staticmethod
281+
@classmethod
272282
def origin_coin_info(
283+
cls,
273284
origin_coins: list[Coin],
274-
singleton_mod: Program = SINGLETON_MOD,
275-
singleton_launcher: Program = SINGLETON_LAUNCHER,
276285
) -> tuple[Coin, Coin, SingletonStruct]:
277286
origin_coin = origin_coins[0]
278287

279-
launcher_hash = singleton_launcher.get_tree_hash()
288+
launcher_hash = cls.singleton_launcher.get_tree_hash()
280289
launcher_coin = Coin(origin_coin.name(), launcher_hash, uint64(1))
281290
launcher_id = launcher_coin.name()
282291
singleton_struct = SingletonStruct(
283-
singleton_mod=singleton_mod, launcher_id=launcher_id, singleton_launcher=singleton_launcher
292+
singleton_mod=cls.singleton_mod, launcher_id=launcher_id, singleton_launcher=cls.singleton_launcher
284293
)
285294

286295
return origin_coin, launcher_coin, singleton_struct
@@ -291,33 +300,32 @@ def launch(
291300
*,
292301
origin_coins: list[Coin],
293302
custody: SelfCustody | PoolingCustody,
294-
memos: list[bytes],
295303
fee: uint64 = uint64(0),
296304
extra_conditions: tuple[Condition, ...] = tuple(),
297-
singleton_mod: Program = SINGLETON_MOD,
298-
singleton_launcher: Program = SINGLETON_LAUNCHER,
299305
) -> tuple[list[Program], list[CoinSpend], Self]:
300-
mod_hash = singleton_mod.get_tree_hash()
301-
launcher_hash = singleton_launcher.get_tree_hash()
302-
origin_coin, launcher_coin, singleton_struct = cls.origin_coin_info(
303-
origin_coins, singleton_mod, singleton_launcher
304-
)
306+
mod_hash = cls.singleton_mod.get_tree_hash()
307+
launcher_hash = cls.singleton_launcher.get_tree_hash()
308+
origin_coin, launcher_coin, singleton_struct = cls.origin_coin_info(origin_coins)
305309
launcher_id = launcher_coin.name()
306310

307311
plotnft_puzzle = PlotNFTPuzzle(singleton_struct=singleton_struct, inner_custody=custody)
308312
rev_puzzle = Program.to(
309313
(
310314
1,
311315
[
312-
CreateCoin(plotnft_puzzle.inner_custody.puzzle_hash(nonce=0), uint64(1), memos=memos).to_program(),
316+
CreateCoin(
317+
plotnft_puzzle.inner_custody.puzzle_hash(nonce=0),
318+
uint64(1),
319+
memo_blob=plotnft_puzzle.inner_custody.puzzle_with_restrictions().memo(),
320+
).to_program(),
313321
CreateCoinAnnouncement(msg=b"").to_program(),
314322
],
315323
)
316324
)
317325
full_rev_singleton_puzzle = puzzle_for_singleton(
318326
launcher_id,
319327
rev_puzzle,
320-
singleton_mod=singleton_mod,
328+
singleton_mod=cls.singleton_mod,
321329
launcher_hash=launcher_hash,
322330
singleton_mod_hash=mod_hash,
323331
)
@@ -335,7 +343,7 @@ def launch(
335343
]
336344
launcher_spend = make_spend(
337345
launcher_coin,
338-
singleton_launcher,
346+
cls.singleton_launcher,
339347
launcher_solution,
340348
)
341349
rev_spend = make_spend(
@@ -361,6 +369,90 @@ def launch(
361369
),
362370
)
363371

372+
@classmethod
373+
def get_next_from_coin_spend(
374+
cls,
375+
*,
376+
coin_spend: CoinSpend,
377+
genesis_challenge: bytes32,
378+
pre_uncurry: UncurriedPuzzle | None = None,
379+
) -> Self:
380+
if pre_uncurry is None:
381+
singleton = uncurry_puzzle(coin_spend.puzzle_reveal)
382+
else:
383+
singleton = pre_uncurry
384+
385+
if singleton.mod != cls.singleton_mod:
386+
raise ValueError("Invalid singleton mod for next PlotNFT")
387+
if singleton.args.at("frr") != cls.singleton_launcher.get_tree_hash(): # TODO: optimize
388+
raise ValueError("Invalid singleton launcher for next PlotNFT")
389+
390+
singleton_struct = SingletonStruct(
391+
singleton_mod=cls.singleton_mod,
392+
singleton_launcher=cls.singleton_launcher,
393+
launcher_id=bytes32(singleton.args.at("frf").as_atom()),
394+
)
395+
396+
inner_puzzle = singleton.args.at("rf")
397+
inner_conditions = parse_conditions_non_consensus(
398+
run(inner_puzzle, Program.from_serialized(coin_spend.solution).at("rrf")).as_iter()
399+
)
400+
singleton_create_coin = next(condition for condition in inner_conditions if isinstance(condition, CreateCoin))
401+
if singleton_create_coin.memo_blob is None:
402+
raise ValueError("Invalid memoization of PlotNFT")
403+
unknown_inner_puzzle = PuzzleWithRestrictions.from_memo(singleton_create_coin.memo_blob)
404+
assert unknown_inner_puzzle.additional_memos is not None
405+
self_custody = SelfCustody(
406+
member=BLSWithTaprootMember(
407+
synthetic_key=G1Element.from_bytes(unknown_inner_puzzle.additional_memos.at("f").as_atom())
408+
)
409+
)
410+
if isinstance(unknown_inner_puzzle.puzzle, MofN):
411+
unknown_inner_puzzle.fill_in_unknown_puzzles(
412+
{SendMessageBanned().puzzle_hash(nonce=0): SendMessageBanned()}
413+
)
414+
pool_puzzle_hash = bytes32(unknown_inner_puzzle.additional_memos.at("rf").as_atom())
415+
timelock = uint64(unknown_inner_puzzle.additional_memos.at("rrf").as_int())
416+
417+
custody: SelfCustody | PoolingCustody = PoolingCustody(
418+
singleton_struct=singleton_struct,
419+
self_custody=self_custody,
420+
pool_puzzle_hash=pool_puzzle_hash,
421+
reward_puzhash=RewardPuzzle(singleton_id=singleton_struct.launcher_id).puzzle_hash(),
422+
timelock=timelock,
423+
exiting=ValidatorStackRestriction(
424+
required_wrappers=[Timelock(timelock), SendMessageBanned()]
425+
).puzzle_hash(nonce=0)
426+
in unknown_inner_puzzle.unknown_puzzles,
427+
genesis_challenge=genesis_challenge,
428+
)
429+
else:
430+
custody = self_custody
431+
432+
return cls(
433+
coin=Coin(
434+
coin_spend.coin.name(),
435+
puzzle_for_singleton(
436+
launcher_id=singleton_struct.launcher_id,
437+
inner_puz=custody.puzzle(nonce=0),
438+
# TODO: optimize
439+
singleton_mod=cls.singleton_mod,
440+
launcher_hash=cls.singleton_launcher.get_tree_hash(),
441+
singleton_mod_hash=cls.singleton_mod.get_tree_hash(),
442+
).get_tree_hash(),
443+
coin_spend.coin.amount,
444+
),
445+
singleton_lineage_proof=LineageProof(
446+
parent_name=coin_spend.coin.parent_coin_info,
447+
inner_puzzle_hash=inner_puzzle.get_tree_hash(),
448+
amount=coin_spend.coin.amount,
449+
),
450+
puzzle=PlotNFTPuzzle(
451+
singleton_struct=singleton_struct,
452+
inner_custody=custody,
453+
),
454+
)
455+
364456
def singleton_action_spend(self, inner_solution: Program) -> CoinSpend:
365457
return make_spend(
366458
coin=self.coin,

chia/wallet/conditions.py

100644100755
Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -229,26 +229,41 @@ class CreateCoin(Condition):
229229
puzzle_hash: bytes32
230230
amount: uint64
231231
memos: list[bytes] | None = None
232+
memo_blob: Program | None = None
233+
234+
def __post_init__(self) -> None:
235+
if self.memos is not None and self.memo_blob is not None:
236+
raise ValueError("Cannot have both memos and memo_blob")
237+
return super().__post_init__()
232238

233239
def to_program(self) -> Program:
234240
condition_args = [ConditionOpcode.CREATE_COIN, self.puzzle_hash, self.amount]
235241
if self.memos is not None:
236242
condition_args.append(self.memos)
243+
elif self.memo_blob is not None:
244+
condition_args.append(self.memo_blob)
237245
condition: Program = Program.to(condition_args)
238246
return condition
239247

240248
@classmethod
241249
def from_program(cls, program: Program) -> Self:
242250
potential_memos: Program = program.at("rrr")
243-
return cls(
244-
bytes32(program.at("rf").as_atom()),
245-
uint64(program.at("rrf").as_int()),
246-
(
247-
None
248-
if potential_memos == Program.NIL
249-
else [memo.as_atom() for memo in potential_memos.at("f").as_iter()]
250-
),
251-
)
251+
try:
252+
return cls(
253+
bytes32(program.at("rf").as_atom()),
254+
uint64(program.at("rrf").as_int()),
255+
(
256+
None
257+
if potential_memos == Program.NIL
258+
else [memo.as_atom() for memo in potential_memos.at("f").as_iter()]
259+
),
260+
)
261+
except ValueError: # a bit of a hack to avoid doing the heavy lifting of switching the memo usage everywhere
262+
return cls(
263+
bytes32(program.at("rf").as_atom()),
264+
uint64(program.at("rrf").as_int()),
265+
memo_blob=potential_memos.at("f"),
266+
)
252267

253268
def as_condition_args(self) -> list[bytes32 | uint64 | list[bytes] | None]:
254269
return [self.puzzle_hash, self.amount, self.memos]

chia/wallet/puzzles/custody/custody_architecture.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,7 @@ class PuzzleWithRestrictions:
226226
nonce: int # Arbitrary nonce to make otherwise identical custody arrangements have different puzzle hashes
227227
restrictions: list[Restriction[MemberOrDPuz]]
228228
puzzle: Puzzle
229+
additional_memos: Program | None = None
229230
spec_namespace: ClassVar[str] = "inner_puzzle_chip?"
230231

231232
def memo(self) -> Program:
@@ -253,11 +254,12 @@ def memo(self) -> Program:
253254
return Program.to(
254255
(
255256
self.spec_namespace,
256-
[
257+
[ # TODO: this should be cons
257258
self.nonce,
258259
[hint.to_program() for hint in restriction_hints],
259260
1 if isinstance(self.puzzle, MofN) else 0,
260261
puzzle_hint.to_program(),
262+
self.additional_memos,
261263
],
262264
)
263265
)
@@ -266,7 +268,9 @@ def memo(self) -> Program:
266268
def from_memo(cls, memo: Program) -> PuzzleWithRestrictions:
267269
if memo.atom is not None or memo.first() != Program.to(cls.spec_namespace):
268270
raise ValueError("Attempting to parse a memo that does not belong to this spec")
269-
nonce, restriction_hints_prog, further_branching_prog, puzzle_hint_prog = memo.rest().as_iter()
271+
nonce, restriction_hints_prog, further_branching_prog, puzzle_hint_prog, additional_memos = (
272+
memo.rest().as_iter()
273+
)
270274
restriction_hints = [RestrictionHint.from_program(hint) for hint in restriction_hints_prog.as_iter()]
271275
further_branching = further_branching_prog != Program.to(None)
272276
if further_branching:
@@ -282,6 +286,7 @@ def from_memo(cls, memo: Program) -> PuzzleWithRestrictions:
282286
nonce=nonce.as_int(),
283287
restrictions=[UnknownRestriction(hint) for hint in restriction_hints],
284288
puzzle=puzzle,
289+
additional_memos=additional_memos if additional_memos != Program.to(None) else None,
285290
)
286291

287292
@property

chia/wallet/trading/offer.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ class NotarizedPayment(CreateCoin):
7171
def from_condition_and_nonce(cls, condition: Program, nonce: bytes32) -> NotarizedPayment:
7272
with_opcode: Program = Program.to((51, condition)) # Gotta do this because the super class is expecting it
7373
p = CreateCoin.from_program(with_opcode)
74-
return cls(p.puzzle_hash, p.amount, p.memos, nonce)
74+
return cls(p.puzzle_hash, p.amount, p.memos, nonce=nonce)
7575

7676
def name(self) -> bytes32:
7777
return self.to_program().get_tree_hash()
@@ -123,7 +123,7 @@ def notarize_payments(
123123
for asset_id, payments in requested_payments.items():
124124
notarized_payments[asset_id] = []
125125
for p in payments:
126-
notarized_payments[asset_id].append(NotarizedPayment(p.puzzle_hash, p.amount, p.memos, nonce))
126+
notarized_payments[asset_id].append(NotarizedPayment(p.puzzle_hash, p.amount, p.memos, nonce=nonce))
127127

128128
return notarized_payments
129129

0 commit comments

Comments
 (0)