import pytest
from hashlib import sha256
from contextlib import asynccontextmanager
from chia.clvm.spend_sim import SimClient, SpendSim
from pathlib import Path
from clvm.casts import int_to_bytes, int_from_bytes
from hsms.streamables.program import Program
from chialisp import compile_clvm
from clvm_tools.binutils import disassemble
from clvm.EvalError import EvalError
from chia.types.mempool_inclusion_status import MempoolInclusionStatus
from chia.util.errors import Err
from dataclasses import dataclass
from typing import Any
from chia_rs import Coin
from chia.types.spend_bundle import SpendBundle
from chia.types.coin_spend import CoinSpend
from blspy import G2Element
from steprun import diag_run_clvm, compile_module_with_symbols
compile_module_with_symbols(['.'],'referee.clsp')
referee = Program.from_bytes(bytes.fromhex(open("referee.clvm.hex").read()))
refhash = referee.tree_hash()
compile_module_with_symbols(['.'],'referee_accuse.clsp')
referee_accuse = Program.from_bytes(bytes.fromhex(open("referee_accuse.clvm.hex").read()))
refaccusehash = referee.tree_hash()
compile_clvm('rockpaperscissorsa.clsp', 'rockpaperscissorsa.clvm.hex', ['.'])
MOD_A = Program.from_bytes(bytes.fromhex(open("rockpaperscissorsa.clvm.hex").read()))
compile_clvm('rockpaperscissorsb.clsp', 'rockpaperscissorsb.clvm.hex', ['.'])
MOD_B = Program.from_bytes(bytes.fromhex(open("rockpaperscissorsb.clvm.hex").read()))
compile_clvm('rockpaperscissorsc.clsp', 'rockpaperscissorsc.clvm.hex', ['.'])
MOD_C = Program.from_bytes(bytes.fromhex(open("rockpaperscissorsc.clvm.hex").read()))
compile_clvm('rockpaperscissorsd.clsp', 'rockpaperscissorsd.clvm.hex', ['.'])
MOD_D = Program.from_bytes(bytes.fromhex(open("rockpaperscissorsd.clvm.hex").read()))
move = 0
accuse = 1
timeout = 2
def drun(prog: Program, *args: Program):
try:
return prog.run(*args)
except EvalError as ee:
print(f"brun -x -y main.sym {prog} {Program.to(list(args))}")
raise
def sha(blob:bytes) -> bytes:
return sha256(blob).digest()
@pytest.fixture(scope="function")
@asynccontextmanager
async def setup_sim() :
sim = await SpendSim.create(db_path=Path("file:db_test?mode=memory&cache=shared"))
sim_client = SimClient(sim)
await sim.farm_block()
try:
yield sim, sim_client
finally:
await sim.close()
def bootstrap_referee(parent_coin_id, initial_validation_program_hash, initial_split,
amount, timeout, max_move_size, mover_puzzle, waiter_puzzle):
puzzle_hash = referee.curry(
[initial_validation_program_hash, 0, initial_split, amount, timeout, max_move_size, mover_puzzle.tree_hash(),
waiter_puzzle.tree_hash(), referee.tree_hash()]).tree_hash()
coin = Coin(parent_coin_id, puzzle_hash, amount)
return RefereeWrap(coin, bytes(32), bytes(32), bytes(32),
initial_validation_program_hash, 0, initial_split, timeout, max_move_size,
mover_puzzle, waiter_puzzle)
@dataclass
class RefereeWrap:
coin: Any
grandparent_id: Any
parent_validation_program_hash: Any
parent_everything_else_hash: Any
validation_program_hash: Any
move: Any
split: Any
timeout: Any
max_move_size: Any
mover_puzzle: Any
waiter_puzzle: Any
def curried_parameters_for_our_puzzle(self, purpose, for_self, move_to_make, split, validation_program_hash):
result = Program.to([
validation_program_hash,
move_to_make,
split,
self.coin.amount,
self.timeout,
self.max_move_size,
self.mover_puzzle.tree_hash() if for_self else self.waiter_puzzle.tree_hash(),
self.waiter_puzzle.tree_hash() if for_self else self.mover_puzzle.tree_hash(),
refhash
])
print(f'for {purpose} curried_parameters_for_our_puzzle is {result}')
return result
def get_puzzle(self):
return referee.curry(self.curried_parameters_for_our_puzzle(
"GET_PUZZLE",
True,
self.move,
self.split,
self.validation_program_hash
))
def SpendMove(self, password, move_to_make, split, validation_program_hash):
print(f"MOVE referee mover_puzzle {self.mover_puzzle.tree_hash()}")
print(f"MOVE referee waiter_puzzle {self.waiter_puzzle.tree_hash()}")
curried_parameters = self.curried_parameters_for_our_puzzle(
"SPEND_MOVE",
False,
move_to_make,
split,
validation_program_hash
)
print(f"MOVE referee curried parameters {curried_parameters}")
new_puzzle_hash = referee.curry(curried_parameters).tree_hash()
print(f"MOVE new puzzle hash {Program.to(new_puzzle_hash)}")
solution = Program.to([move, move_to_make, split, validation_program_hash, self.mover_puzzle,
[password, [51, new_puzzle_hash, self.coin.amount]]])
coin = Coin(self.coin.name(), new_puzzle_hash, self.coin.amount)
everything_else_hash = Program.to([self.move, self.split, self.coin.amount, self.timeout,
self.max_move_size, self.mover_puzzle.tree_hash(), self.waiter_puzzle.tree_hash(),
referee.tree_hash()]).tree_hash()
return (solution, RefereeWrap(coin, self.coin.parent_coin_info, self.validation_program_hash, everything_else_hash,
validation_program_hash, move_to_make, split, self.timeout, self.max_move_size,
self.waiter_puzzle, self.mover_puzzle))
def SpendAccuse(self, password):
print(f"ACCUSE starting with puzzle hash {Program.to(self.get_puzzle().tree_hash())}")
print(f"ACCUSE parent_id {Program.to(self.coin.parent_coin_info)}")
print(f"ACCUSE referee mover_puzzle {self.mover_puzzle.tree_hash()}")
print(f"ACCUSE referee waiter_puzzle {self.waiter_puzzle.tree_hash()}")
new_puzzle_hash = referee_accuse.curry([
self.parent_validation_program_hash,
self.validation_program_hash,
self.move,
self.split,
self.coin.amount,
self.timeout,
self.waiter_puzzle.tree_hash(),
self.mover_puzzle.tree_hash()
]).tree_hash()
solution = Program.to([accuse, self.grandparent_id, self.parent_validation_program_hash,
self.parent_everything_else_hash, self.mover_puzzle, [password, [51, new_puzzle_hash, self.coin.amount]]])
coin = Coin(self.coin.name(), new_puzzle_hash, self.coin.amount)
return (solution, RefereeAccuseWrap(coin, self.parent_validation_program_hash, self.validation_program_hash,
self.move, self.split, self.timeout, self.waiter_puzzle.tree_hash(),
self.mover_puzzle.tree_hash()))
def SpendTimeout(self):
movercoinid = Coin(self.coin.name(), self.mover_puzzle.tree_hash(), self.split).name()
waitercoinid = Coin(self.coin.name(), self.waiter_puzzle.tree_hash(),
self.coin.amount - self.split).name()
return (Program.to((timeout, 0)), movercoinid, waitercoinid)
@dataclass
class RefereeAccuseWrap:
coin: Any
old_validation_puzzle_hash: Any
new_validation_puzzle_hash: Any
move: Any
split: Any
timeout: Any
accused_puzzle_hash: Any
accuser_puzzle_hash: Any
def get_puzzle(self):
return referee_accuse.curry([self.old_validation_puzzle_hash, self.new_validation_puzzle_hash,
self.move, self.split, self.coin.amount, self.timeout, self.accused_puzzle_hash,
self.accuser_puzzle_hash])
def SpendTimeout(self):
coin = Coin(self.coin.name(), self.accuser_puzzle_hash, self.coin.amount)
return (Program.to(0), coin.name())
def SpendDefend(self, validation_program_reveal, validation_program_solution):
solution = Program.to([validation_program_reveal, validation_program_solution])
coin = Coin(self.coin.name(), self.accused_puzzle_hash, self.coin.amount)
return (solution, coin.name())
@pytest.mark.asyncio
@pytest.mark.parametrize('amove', [0, 1, 2])
@pytest.mark.parametrize('bmove', [0, 1, 2])
async def test_rps(amove, bmove, setup_sim):
total = 100
alice_final = (total//2 if amove == bmove else (0 if bmove == (amove + 1) % 3 else total))
alice_preimage = int_to_bytes(60 + amove)
alice_image = sha(alice_preimage)
bob_preimage = int_to_bytes(60 + bmove)
bob_image = sha(bob_preimage)
alice_move = int_to_bytes(amove)
nil = Program.to(0)
alice_puzzle = Program.from_bytes(bytes.fromhex('ff02ffff03ffff09ff02ffff0185616c69636580ffff0103ffff01ff088080ff0180'))
alice_puzzle_hash = alice_puzzle.tree_hash()
bob_puzzle = Program.from_bytes(bytes.fromhex('ff02ffff03ffff09ff02ffff0183626f6280ffff0103ffff01ff088080ff0180'))
bob_puzzle_hash = bob_puzzle.tree_hash()
async with setup_sim as (sym, client):
acs = Program.to(1)
acs_hash = acs.tree_hash()
await sym.farm_block(acs_hash)
mycoin = (await client.get_coin_records_by_puzzle_hashes([acs_hash], include_spent_coins = False))[0].coin
referee = bootstrap_referee(mycoin.name(), MOD_A.tree_hash(), 2, total, 1000, 50, alice_puzzle, bob_puzzle)
(status, err) = await client.push_tx(SpendBundle([CoinSpend(mycoin, acs, Program.to([[51, referee.coin.puzzle_hash,
referee.coin.amount]]))], G2Element()))
assert status == MempoolInclusionStatus.SUCCESS
await sym.farm_block()
savepoint = sym.block_height
solution, accuse = referee.SpendAccuse('alice')
(status, err) = await client.push_tx(SpendBundle([CoinSpend(referee.coin, referee.get_puzzle(),
solution)], G2Element()))
print(solution)
print(accuse.get_puzzle())
assert status == MempoolInclusionStatus.FAILED
assert err == Err.ASSERT_MY_PARENT_ID_FAILED
solution, alice_reward_id, bob_reward_id = referee.SpendTimeout()
spend = SpendBundle([CoinSpend(referee.coin, referee.get_puzzle(), solution)], G2Element())
(status, err) = await client.push_tx(spend)
assert status == MempoolInclusionStatus.FAILED
assert err == Err.ASSERT_SECONDS_RELATIVE_FAILED
sym.pass_time(2000)
await sym.farm_block()
(status, err) = await client.push_tx(spend)
assert status == MempoolInclusionStatus.SUCCESS
assert err is None
await sym.farm_block()
assert (await client.get_coin_records_by_names([alice_reward_id], include_spent_coins = False))[0].coin.amount == 2
assert (await client.get_coin_records_by_names([bob_reward_id], include_spent_coins = False))[0].coin.amount == total - 2
await sym.rewind(savepoint)
solution, ref2 = referee.SpendMove('alice', bytes(100), 0, bytes(32))
(status, err) = await client.push_tx(SpendBundle([CoinSpend(referee.coin, referee.get_puzzle(),
solution)], G2Element()))
assert status == MempoolInclusionStatus.FAILED
assert err == Err.GENERATOR_RUNTIME_ERROR
solution, ref2 = referee.SpendMove('alice', 'abc', -1, bytes(32))
(status, err) = await client.push_tx(SpendBundle([CoinSpend(referee.coin, referee.get_puzzle(),
solution)], G2Element()))
assert status == MempoolInclusionStatus.FAILED
assert err == Err.GENERATOR_RUNTIME_ERROR
solution, ref2 = referee.SpendMove('alice', 'abc', referee.coin.amount + 1, bytes(32))
(status, err) = await client.push_tx(SpendBundle([CoinSpend(referee.coin, referee.get_puzzle(),
solution)], G2Element()))
assert status == MempoolInclusionStatus.FAILED
assert err == Err.GENERATOR_RUNTIME_ERROR
bpuz = MOD_B.curry(alice_image)
solution, ref2 = referee.SpendMove('alice', alice_image, 0, bpuz.tree_hash())
(status, err) = await client.push_tx(SpendBundle([CoinSpend(referee.coin, referee.get_puzzle(),
solution)], G2Element()))
assert status == MempoolInclusionStatus.SUCCESS
await sym.farm_block()
savepoint = sym.block_height
solution, accuse = ref2.SpendAccuse('bob')
(status, err) = await client.push_tx(SpendBundle([CoinSpend(ref2.coin, ref2.get_puzzle(),
solution)], G2Element()))
assert status == MempoolInclusionStatus.SUCCESS
await sym.farm_block()
savepoint2 = sym.block_height
solution, reward_id = accuse.SpendDefend(MOD_A, nil)
print(solution)
(status, err) = await client.push_tx(SpendBundle([CoinSpend(accuse.coin, accuse.get_puzzle(),
solution)], G2Element()))
assert status == MempoolInclusionStatus.SUCCESS
await sym.farm_block()
reward_coin_wrapper = await client.get_coin_records_by_names([reward_id], include_spent_coins =
False)
reward_coin = reward_coin_wrapper[0].coin
assert reward_coin.amount == referee.coin.amount
assert reward_coin.puzzle_hash == alice_puzzle_hash
await sym.rewind(savepoint2)
solution, reward_id = accuse.SpendTimeout()
spend = SpendBundle([CoinSpend(accuse.coin, accuse.get_puzzle(), solution)], G2Element())
(status, err) = await client.push_tx(spend)
assert status == MempoolInclusionStatus.FAILED
assert err == Err.ASSERT_SECONDS_RELATIVE_FAILED
sym.pass_time(2000)
await sym.farm_block()
(status, err) = await client.push_tx(spend)
assert status == MempoolInclusionStatus.SUCCESS
assert err is None
await sym.farm_block()
reward_coin_wrapper = await client.get_coin_records_by_names([reward_id], include_spent_coins =
False)
reward_coin = reward_coin_wrapper[0].coin
assert reward_coin.amount == referee.coin.amount
assert reward_coin.puzzle_hash == bob_puzzle_hash
await sym.rewind(savepoint)
cpuz = MOD_C.curry([alice_image, bob_image])
solution, ref3 = ref2.SpendMove('bob', bob_image, 0, cpuz.tree_hash())
(status, err) = await client.push_tx(SpendBundle([CoinSpend(ref2.coin, ref2.get_puzzle(),
solution)], G2Element()))
assert status == MempoolInclusionStatus.SUCCESS
await sym.farm_block()
savepoint = sym.block_height
solution, accuse = ref3.SpendAccuse('alice')
(status, err) = await client.push_tx(SpendBundle([CoinSpend(ref3.coin, ref3.get_puzzle(),
solution)], G2Element()))
assert status == MempoolInclusionStatus.SUCCESS
await sym.farm_block()
solution, reward_id = accuse.SpendDefend(bpuz, nil)
(status, err) = await client.push_tx(SpendBundle([CoinSpend(accuse.coin, accuse.get_puzzle(),
solution)], G2Element()))
assert status == MempoolInclusionStatus.SUCCESS
await sym.farm_block()
reward_coin = (await client.get_coin_records_by_names([reward_id], include_spent_coins =
False))[0].coin
assert reward_coin.amount == referee.coin.amount
assert reward_coin.puzzle_hash == bob_puzzle_hash
await sym.rewind(savepoint)
alice_bad_preimage = int_to_bytes(61 + amove)
dpuz = MOD_D.curry([(amove + 1) % 3, bob_image])
solution, ref4 = ref3.SpendMove('alice', alice_bad_preimage, 0, dpuz.tree_hash())
(status, err) = await client.push_tx(SpendBundle([CoinSpend(ref3.coin, ref3.get_puzzle(),
solution)], G2Element()))
assert status == MempoolInclusionStatus.SUCCESS
await sym.farm_block()
solution, accuse = ref4.SpendAccuse('bob')
(status, err) = await client.push_tx(SpendBundle([CoinSpend(ref4.coin, ref4.get_puzzle(),
solution)], G2Element()))
assert status == MempoolInclusionStatus.SUCCESS
await sym.farm_block()
solution, reward_id = accuse.SpendDefend(cpuz, nil)
(status, err) = await client.push_tx(SpendBundle([CoinSpend(accuse.coin, accuse.get_puzzle(),
solution)], G2Element()))
assert status == MempoolInclusionStatus.FAILED
assert err == Err.GENERATOR_RUNTIME_ERROR
await sym.rewind(savepoint)
dpuz = MOD_D.curry([alice_move, bob_image])
solution, ref4 = ref3.SpendMove('alice', alice_preimage, 0, dpuz.tree_hash())
(status, err) = await client.push_tx(SpendBundle([CoinSpend(ref3.coin, ref3.get_puzzle(),
solution)], G2Element()))
assert status == MempoolInclusionStatus.SUCCESS
await sym.farm_block()
savepoint = sym.block_height
solution, accuse = ref4.SpendAccuse('bob')
(status, err) = await client.push_tx(SpendBundle([CoinSpend(ref4.coin, ref4.get_puzzle(),
solution)], G2Element()))
assert status == MempoolInclusionStatus.SUCCESS
await sym.farm_block()
solution, reward_id = accuse.SpendDefend(cpuz, nil)
(status, err) = await client.push_tx(SpendBundle([CoinSpend(accuse.coin, accuse.get_puzzle(),
solution)], G2Element()))
assert status == MempoolInclusionStatus.SUCCESS
await sym.rewind(savepoint)
bob_bad_preimage = int_to_bytes(121 + amove)
solution, ref5 = ref4.SpendMove('bob', bob_bad_preimage, 0, dpuz.tree_hash())
(status, err) = await client.push_tx(SpendBundle([CoinSpend(ref4.coin, ref4.get_puzzle(),
solution)], G2Element()))
assert status == MempoolInclusionStatus.SUCCESS
await sym.farm_block()
solution, accuse = ref5.SpendAccuse('alice')
(status, err) = await client.push_tx(SpendBundle([CoinSpend(ref5.coin, ref5.get_puzzle(),
solution)], G2Element()))
assert status == MempoolInclusionStatus.SUCCESS
await sym.farm_block()
solution, reward_id = accuse.SpendDefend(dpuz, nil)
(status, err) = await client.push_tx(SpendBundle([CoinSpend(accuse.coin, accuse.get_puzzle(),
solution)], G2Element()))
assert status == MempoolInclusionStatus.FAILED
assert err == Err.GENERATOR_RUNTIME_ERROR
solution, reward_id = accuse.SpendDefend(acs, nil)
(status, err) = await client.push_tx(SpendBundle([CoinSpend(accuse.coin, accuse.get_puzzle(),
solution)], G2Element()))
assert status == MempoolInclusionStatus.FAILED
assert err == Err.GENERATOR_RUNTIME_ERROR
await sym.rewind(savepoint)
if amove == bmove:
solution, ref5 = ref4.SpendMove('bob', bob_preimage, 0, dpuz.tree_hash())
(status, err) = await client.push_tx(SpendBundle([CoinSpend(ref4.coin, ref4.get_puzzle(),
solution)], G2Element()))
assert status == MempoolInclusionStatus.SUCCESS
await sym.farm_block()
solution, accuse = ref5.SpendAccuse('alice')
(status, err) = await client.push_tx(SpendBundle([CoinSpend(ref5.coin, ref5.get_puzzle(),
solution)], G2Element()))
assert status == MempoolInclusionStatus.SUCCESS
await sym.farm_block()
solution, reward_id = accuse.SpendDefend(dpuz, nil)
(status, err) = await client.push_tx(SpendBundle([CoinSpend(accuse.coin, accuse.get_puzzle(),
solution)], G2Element()))
assert status == MempoolInclusionStatus.FAILED
assert err == Err.GENERATOR_RUNTIME_ERROR
await sym.rewind(savepoint)
solution, ref5 = ref4.SpendMove('bob', bob_preimage, alice_final, nil)
(status, err) = await client.push_tx(SpendBundle([CoinSpend(ref4.coin, ref4.get_puzzle(),
solution)], G2Element()))
assert status == MempoolInclusionStatus.SUCCESS
await sym.farm_block()
savepoint = sym.block_height
solution, ref6 = ref5.SpendMove('alice', nil, 0, nil)
(status, err) = await client.push_tx(SpendBundle([CoinSpend(ref5.coin, ref5.get_puzzle(),
solution)], G2Element()))
assert status == MempoolInclusionStatus.FAILED
assert err == Err.GENERATOR_RUNTIME_ERROR
sym.pass_time(2000)
await sym.farm_block()
solution, alice_reward_id, bob_reward_id = ref5.SpendTimeout()
spend = SpendBundle([CoinSpend(ref5.coin, ref5.get_puzzle(), solution)], G2Element())
(status, err) = await client.push_tx(spend)
assert status == MempoolInclusionStatus.SUCCESS
assert err is None
await sym.farm_block()
if alice_final != 0:
assert (await client.get_coin_records_by_names([alice_reward_id], include_spent_coins = False))[0].coin.amount == alice_final
else:
assert len(await client.get_coin_records_by_names([alice_reward_id], include_spent_coins = False)) == 0
if alice_final != ref5.coin.amount:
assert (await client.get_coin_records_by_names([bob_reward_id], include_spent_coins = False))[0].coin.amount == ref5.coin.amount - alice_final
else:
assert len(await client.get_coin_records_by_names([bob_reward_id], include_spent_coins = False)) == 0
await sym.rewind(savepoint)
solution, accuse = ref5.SpendAccuse('alice')
(status, err) = await client.push_tx(SpendBundle([CoinSpend(ref5.coin, ref5.get_puzzle(),
solution)], G2Element()))
assert status == MempoolInclusionStatus.SUCCESS
await sym.farm_block()
solution, reward_id = accuse.SpendDefend(dpuz, nil)
(status, err) = await client.push_tx(SpendBundle([CoinSpend(accuse.coin, accuse.get_puzzle(),
solution)], G2Element()))
assert (status, err) == (MempoolInclusionStatus.SUCCESS, None)