Skip to content

Commit

Permalink
New clvm generator api (#9645)
Browse files Browse the repository at this point in the history
* use the new run_generator2() and run_chia_program() API of clvm_rs

* bump clvm_rs dependency to 0.1.17
  • Loading branch information
arvidn committed Jan 11, 2022
1 parent c143b71 commit 0ba838b
Show file tree
Hide file tree
Showing 5 changed files with 81 additions and 40 deletions.
78 changes: 65 additions & 13 deletions chia/full_node/mempool_check_conditions.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,20 @@
from typing import Dict, List, Optional
from clvm_rs import STRICT_MODE as MEMPOOL_MODE

from clvm.casts import int_from_bytes, int_to_bytes
from chia.consensus.cost_calculator import NPCResult
from chia.full_node.generator import create_generator_args, setup_generator_args
from chia.types.blockchain_format.program import NIL
from chia.types.coin_record import CoinRecord
from chia.types.condition_with_args import ConditionWithArgs
from chia.types.generator_types import BlockGenerator
from chia.types.name_puzzle_condition import NPC
from chia.util.clvm import int_from_bytes
from chia.util.condition_tools import ConditionOpcode
from chia.util.errors import Err
from chia.util.ints import uint32, uint64, uint16
from chia.wallet.puzzles.generator_loader import GENERATOR_FOR_SINGLE_COIN_MOD
from chia.wallet.puzzles.rom_bootstrap_generator import get_generator
from chia.consensus.cost_calculator import conditions_cost

GENERATOR_MOD = get_generator()

Expand Down Expand Up @@ -87,6 +88,26 @@ def mempool_assert_relative_time_exceeds(
return None


def add_int_cond(
conds: Dict[ConditionOpcode, List[ConditionWithArgs]],
op: ConditionOpcode,
arg: int,
):
if op not in conds:
conds[op] = []
conds[op].append(ConditionWithArgs(op, [int_to_bytes(arg)]))


def add_cond(
conds: Dict[ConditionOpcode, List[ConditionWithArgs]],
op: ConditionOpcode,
args: List[bytes],
):
if op not in conds:
conds[op] = []
conds[op].append(ConditionWithArgs(op, args))


def get_name_puzzle_conditions(
generator: BlockGenerator, max_cost: int, *, cost_per_byte: int, mempool_mode: bool
) -> NPCResult:
Expand All @@ -97,20 +118,51 @@ def get_name_puzzle_conditions(

flags = MEMPOOL_MODE if mempool_mode else 0
try:
err, result, clvm_cost = GENERATOR_MOD.run_as_generator(max_cost, flags, block_program, block_program_args)
err, result = GENERATOR_MOD.run_as_generator(max_cost, flags, block_program, block_program_args)

if err is not None:
assert err != 0
return NPCResult(uint16(err), [], uint64(0))
else:
npc_list = []
for r in result:
conditions = []
for c in r.conditions:
cwa = []
for cond_list in c[1]:
cwa.append(ConditionWithArgs(ConditionOpcode(bytes([cond_list.opcode])), cond_list.vars))
conditions.append((ConditionOpcode(bytes([c[0]])), cwa))
npc_list.append(NPC(r.coin_name, r.puzzle_hash, conditions))
return NPCResult(None, npc_list, uint64(clvm_cost))

condition_cost = 0
first = True
npc_list = []
for r in result.spends:
conditions: Dict[ConditionOpcode, List[ConditionWithArgs]] = {}
if r.height_relative is not None:
add_int_cond(conditions, ConditionOpcode.ASSERT_HEIGHT_RELATIVE, r.height_relative)
if r.seconds_relative > 0:
add_int_cond(conditions, ConditionOpcode.ASSERT_SECONDS_RELATIVE, r.seconds_relative)
for cc in r.create_coin:
if cc[2] == b"":
add_cond(conditions, ConditionOpcode.CREATE_COIN, [cc[0], int_to_bytes(cc[1])])
else:
add_cond(conditions, ConditionOpcode.CREATE_COIN, [cc[0], int_to_bytes(cc[1]), cc[2]])
for sig in r.agg_sig_me:
add_cond(conditions, ConditionOpcode.AGG_SIG_ME, [sig[0], sig[1]])

# all conditions that aren't tied to a specific spent coin, we roll into the first one
if first:
first = False
if result.reserve_fee > 0:
add_int_cond(conditions, ConditionOpcode.RESERVE_FEE, result.reserve_fee)
if result.height_absolute > 0:
add_int_cond(conditions, ConditionOpcode.ASSERT_HEIGHT_ABSOLUTE, result.height_absolute)
if result.seconds_absolute > 0:
add_int_cond(conditions, ConditionOpcode.ASSERT_SECONDS_ABSOLUTE, result.seconds_absolute)
for sig in result.agg_sig_unsafe:
add_cond(conditions, ConditionOpcode.AGG_SIG_UNSAFE, [sig[0], sig[1]])

condition_cost += conditions_cost(conditions)
npc_list.append(NPC(r.coin_id, r.puzzle_hash, [(op, cond) for op, cond in conditions.items()]))

# this is a temporary hack. The NPCResult clvm_cost field is not
# supposed to include conditions cost # but the result from run_generator2() does
# include that cost. So, until we change which cost we include in NPCResult,
# subtract the conditions cost. The pure CLVM cost is what will remain.
clvm_cost = result.cost - condition_cost
return NPCResult(None, npc_list, uint64(clvm_cost))

except BaseException as e:
log.debug(f"get_name_puzzle_condition failed: {e}")
return NPCResult(uint16(Err.GENERATOR_RUNTIME_ERROR.value), [], uint64(0))
Expand Down
29 changes: 9 additions & 20 deletions chia/types/blockchain_format/program.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
import io
from typing import List, Set, Tuple, Optional, Any

from clvm import KEYWORD_FROM_ATOM, KEYWORD_TO_ATOM, SExp
from clvm import SExp
from clvm import run_program as default_run_program
from clvm.casts import int_from_bytes
from clvm.EvalError import EvalError
from clvm.operators import OP_REWRITE, OPERATOR_LOOKUP
from clvm.operators import OPERATOR_LOOKUP
from clvm.serialize import sexp_from_stream, sexp_to_stream
from clvm_rs import STRICT_MODE as MEMPOOL_MODE, deserialize_and_run_program2, serialized_length, run_generator
from clvm_rs import STRICT_MODE as MEMPOOL_MODE, run_chia_program, serialized_length, run_generator2
from clvm_tools.curry import curry, uncurry

from chia.types.blockchain_format.sized_bytes import bytes32
from chia.util.hash import std_hash
from chia.util.ints import uint16
from chia.util.byte_types import hexstr_to_bytes

from .tree_hash import sha256_treehash
Expand Down Expand Up @@ -243,7 +244,9 @@ def run_mempool_with_cost(self, max_cost: int, *args) -> Tuple[int, Program]:
def run_with_cost(self, max_cost: int, *args) -> Tuple[int, Program]:
return self._run(max_cost, 0, *args)

def run_as_generator(self, max_cost: int, flags: int, *args) -> Tuple[Optional[int], List[Any], int]:
# returns an optional error code and an optional PySpendBundleConditions (from clvm_rs)
# exactly one of those will hold a value
def run_as_generator(self, max_cost: int, flags: int, *args) -> Tuple[Optional[uint16], Optional[Any]]:
serialized_args = b""
if len(args) > 1:
# when we have more than one argument, serialize them into a list
Expand All @@ -254,19 +257,12 @@ def run_as_generator(self, max_cost: int, flags: int, *args) -> Tuple[Optional[i
else:
serialized_args += _serialize(args[0])

native_opcode_names_by_opcode = dict(
("op_%s" % OP_REWRITE.get(k, k), op) for op, k in KEYWORD_FROM_ATOM.items() if k not in "qa."
)
err, npc_list, cost = run_generator(
return run_generator2(
self._buf,
serialized_args,
KEYWORD_TO_ATOM["q"][0],
KEYWORD_TO_ATOM["a"][0],
native_opcode_names_by_opcode,
max_cost,
flags,
)
return None if err == 0 else err, npc_list, cost

def _run(self, max_cost: int, flags, *args) -> Tuple[int, Program]:
# when multiple arguments are passed, concatenate them into a serialized
Expand All @@ -283,16 +279,9 @@ def _run(self, max_cost: int, flags, *args) -> Tuple[int, Program]:
else:
serialized_args += _serialize(args[0])

# TODO: move this ugly magic into `clvm` "dialects"
native_opcode_names_by_opcode = dict(
("op_%s" % OP_REWRITE.get(k, k), op) for op, k in KEYWORD_FROM_ATOM.items() if k not in "qa."
)
cost, ret = deserialize_and_run_program2(
cost, ret = run_chia_program(
self._buf,
serialized_args,
KEYWORD_TO_ATOM["q"][0],
KEYWORD_TO_ATOM["a"][0],
native_opcode_names_by_opcode,
max_cost,
flags,
)
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"chiabip158==1.0", # bip158-style wallet filters
"chiapos==1.0.7", # proof of space
"clvm==0.9.7",
"clvm_rs==0.1.16",
"clvm_rs==0.1.17",
"clvm_tools==0.4.3",
"aiohttp==3.7.4", # HTTP server for full node rpc
"aiosqlite==0.17.0", # asyncio wrapper for sqlite, to store blocks
Expand Down
10 changes: 5 additions & 5 deletions tests/core/full_node/test_mempool.py
Original file line number Diff line number Diff line change
Expand Up @@ -1872,7 +1872,7 @@ def test_create_coin_different_parent(self):
assert c.conditions == [
(
opcode.value,
[ConditionWithArgs(opcode, [puzzle_hash.encode("ascii"), bytes([10]), b""])],
[ConditionWithArgs(opcode, [puzzle_hash.encode("ascii"), bytes([10])])],
)
]

Expand All @@ -1886,11 +1886,11 @@ def test_create_coin_different_puzzhash(self):
assert len(npc_result.npc_list) == 1
opcode = ConditionOpcode.CREATE_COIN
assert (
ConditionWithArgs(opcode, [puzzle_hash_1.encode("ascii"), bytes([5]), b""])
ConditionWithArgs(opcode, [puzzle_hash_1.encode("ascii"), bytes([5])])
in npc_result.npc_list[0].conditions[0][1]
)
assert (
ConditionWithArgs(opcode, [puzzle_hash_2.encode("ascii"), bytes([5]), b""])
ConditionWithArgs(opcode, [puzzle_hash_2.encode("ascii"), bytes([5])])
in npc_result.npc_list[0].conditions[0][1]
)

Expand All @@ -1903,11 +1903,11 @@ def test_create_coin_different_amounts(self):
assert len(npc_result.npc_list) == 1
opcode = ConditionOpcode.CREATE_COIN
assert (
ConditionWithArgs(opcode, [puzzle_hash.encode("ascii"), bytes([5]), b""])
ConditionWithArgs(opcode, [puzzle_hash.encode("ascii"), bytes([5])])
in npc_result.npc_list[0].conditions[0][1]
)
assert (
ConditionWithArgs(opcode, [puzzle_hash.encode("ascii"), bytes([4]), b""])
ConditionWithArgs(opcode, [puzzle_hash.encode("ascii"), bytes([4])])
in npc_result.npc_list[0].conditions[0][1]
)

Expand Down
2 changes: 1 addition & 1 deletion tests/generator/test_rom.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ def test_get_name_puzzle_conditions(self):
npc_result = get_name_puzzle_conditions(gen, max_cost=MAX_COST, cost_per_byte=COST_PER_BYTE, mempool_mode=False)
assert npc_result.error is None
assert npc_result.clvm_cost == EXPECTED_COST
cond_1 = ConditionWithArgs(ConditionOpcode.CREATE_COIN, [bytes([0] * 31 + [1]), int_to_bytes(500), b""])
cond_1 = ConditionWithArgs(ConditionOpcode.CREATE_COIN, [bytes([0] * 31 + [1]), int_to_bytes(500)])
CONDITIONS = [
(ConditionOpcode.CREATE_COIN, [cond_1]),
]
Expand Down

0 comments on commit 0ba838b

Please sign in to comment.