From 46aed1e08b9fee6918eeed1e0130a65258dc5edc Mon Sep 17 00:00:00 2001 From: Richard Kiss Date: Fri, 7 May 2021 16:36:01 -0700 Subject: [PATCH 01/11] First crack at `Dialect`.# --- clvm/__init__.py | 3 +- clvm/chia_dialect.py | 48 +++++++++++++++ clvm/dialect.py | 69 +++++++++++++++++++++ clvm/handle_unknown_op.py | 121 +++++++++++++++++++++++++++++++++++++ clvm/operators.py | 120 ++++++------------------------------ clvm/run_program.py | 62 ++++++++++++++++++- tests/operatordict_test.py | 29 --------- tests/operators_test.py | 29 +++++---- 8 files changed, 335 insertions(+), 146 deletions(-) create mode 100644 clvm/chia_dialect.py create mode 100644 clvm/dialect.py create mode 100644 clvm/handle_unknown_op.py delete mode 100644 tests/operatordict_test.py diff --git a/clvm/__init__.py b/clvm/__init__.py index a7062e1e..b0c4bf4c 100644 --- a/clvm/__init__.py +++ b/clvm/__init__.py @@ -1,6 +1,7 @@ from .SExp import SExp +from .dialect import Dialect # noqa from .operators import ( # noqa - QUOTE_ATOM, + QUOTE_ATOM, # deprecated KEYWORD_TO_ATOM, KEYWORD_FROM_ATOM, ) diff --git a/clvm/chia_dialect.py b/clvm/chia_dialect.py new file mode 100644 index 00000000..7acdaeae --- /dev/null +++ b/clvm/chia_dialect.py @@ -0,0 +1,48 @@ +from . import core_ops, more_ops + +from .casts import int_to_bytes +from .dialect import DialectInfo +from .op_utils import operators_for_module + + +KEYWORDS = ( + # core opcodes 0x01-x08 + ". q a i c f r l x " + # opcodes on atoms as strings 0x09-0x0f + "= >s sha256 substr strlen concat . " + # opcodes on atoms as ints 0x10-0x17 + "+ - * / divmod > ash lsh " + # opcodes on atoms as vectors of bools 0x18-0x1c + "logand logior logxor lognot . " + # opcodes for bls 1381 0x1d-0x1f + "point_add pubkey_for_exp . " + # bool opcodes 0x20-0x23 + "not any all . " + # misc 0x24 + "softfork " +).split() + +KEYWORD_FROM_ATOM = {int_to_bytes(k): v for k, v in enumerate(KEYWORDS)} +KEYWORD_TO_ATOM = {v: k for k, v in KEYWORD_FROM_ATOM.items()} + +OP_REWRITE = { + "+": "add", + "-": "subtract", + "*": "multiply", + "/": "div", + "i": "if", + "c": "cons", + "f": "first", + "r": "rest", + "l": "listp", + "x": "raise", + "=": "eq", + ">": "gr", + ">s": "gr_bytes", +} + + +def chia_dialect_info(): + op_lookup = operators_for_module(KEYWORD_TO_ATOM, core_ops, OP_REWRITE) + op_lookup.update(operators_for_module(KEYWORD_TO_ATOM, more_ops, OP_REWRITE)) + return DialectInfo(KEYWORD_TO_ATOM["q"], KEYWORD_TO_ATOM["a"], op_lookup) diff --git a/clvm/dialect.py b/clvm/dialect.py new file mode 100644 index 00000000..57dceee2 --- /dev/null +++ b/clvm/dialect.py @@ -0,0 +1,69 @@ +from dataclasses import dataclass + +from typing import Any, Callable, Dict, Optional, Tuple, Union + +from .run_program import _run_program + +CLVMAtom = Any +CLVMPair = Any + +CLVMObjectType = Union["CLVMAtom", "CLVMPair"] + + +MultiOpFn = Callable[[bytes, CLVMObjectType, int], Tuple[int, CLVMObjectType]] + +OpFn = Callable[[CLVMObjectType, int], Tuple[int, CLVMObjectType]] + +OperatorDict = Dict[bytes, Callable[[CLVMObjectType, int], Tuple[int, CLVMObjectType]]] + + +@dataclass +class ChainableMultiOpFn(MultiOpFn): + op_lookup: OperatorDict + unknown_op_handler: MultiOpFn + + def __call__(self, op: bytes, arguments: CLVMObjectType, max_cost: int) -> Tuple[int, CLVMObjectType]: + f = self.op_lookup.get(op) + if f: + try: + return f(arguments) + except TypeError: + # some operators require `max_cost` + return f(arguments, max_cost) + return self.unknown_op_handler(op, arguments, max_cost) + + +@dataclass +class DialectInfo: + quote_kw: bytes + apply_kw: bytes + opcode_lookup: OperatorDict + + +class Dialect: + def __init__( + self, + dialect_info: DialectInfo, + unknown_callback: MultiOpFn, + ): + self.dialect_info = dialect_info + self.multi_op_fn = ChainableMultiOpFn(self.dialect_info.opcode_lookup, unknown_callback) + + def run_program( + self, + program: CLVMObjectType, + env: CLVMObjectType, + max_cost: int, + pre_eval_f: Optional[ + Callable[[CLVMObjectType, CLVMObjectType], Tuple[int, CLVMObjectType]] + ] = None, + ) -> Tuple[int, CLVMObjectType]: + return _run_program( + program, + env, + self.multi_op_fn, + self.dialect_info.quote_kw, + self.dialect_info.apply_kw, + max_cost, + pre_eval_f + ) diff --git a/clvm/handle_unknown_op.py b/clvm/handle_unknown_op.py new file mode 100644 index 00000000..c1250a43 --- /dev/null +++ b/clvm/handle_unknown_op.py @@ -0,0 +1,121 @@ +from typing import Tuple + +from .CLVMObject import CLVMObject +from .EvalError import EvalError + +from .costs import ( + ARITH_BASE_COST, + ARITH_COST_PER_LIMB_DIVIDER, + ARITH_COST_PER_ARG, + MUL_BASE_COST, + MUL_COST_PER_OP, + MUL_LINEAR_COST_PER_BYTE_DIVIDER, + MUL_SQUARE_COST_PER_BYTE_DIVIDER, + CONCAT_BASE_COST, + CONCAT_COST_PER_ARG, + CONCAT_COST_PER_BYTE_DIVIDER, +) + + +def handle_unknown_op_strict(op, arguments, _max_cost=None): + raise EvalError("unimplemented operator", arguments.to(op)) + + +def args_len(op_name, args): + for arg in args.as_iter(): + if arg.pair: + raise EvalError("%s requires int args" % op_name, arg) + yield len(arg.as_atom()) + + +# unknown ops are reserved if they start with 0xffff +# otherwise, unknown ops are no-ops, but they have costs. The cost is computed +# like this: + +# byte index (reverse): +# | 4 | 3 | 2 | 1 | 0 | +# +---+---+---+---+------------+ +# | multiplier |XX | XXXXXX | +# +---+---+---+---+---+--------+ +# ^ ^ ^ +# | | + 6 bits ignored when computing cost +# cost_multiplier | +# + 2 bits +# cost_function + +# 1 is always added to the multiplier before using it to multiply the cost, this +# is since cost may not be 0. + +# cost_function is 2 bits and defines how cost is computed based on arguments: +# 0: constant, cost is 1 * (multiplier + 1) +# 1: computed like operator add, multiplied by (multiplier + 1) +# 2: computed like operator mul, multiplied by (multiplier + 1) +# 3: computed like operator concat, multiplied by (multiplier + 1) + +# this means that unknown ops where cost_function is 1, 2, or 3, may still be +# fatal errors if the arguments passed are not atoms. + +def handle_unknown_op_softfork_ready(op: bytes, args: CLVMObject) -> Tuple[int, CLVMObject]: + # any opcode starting with ffff is reserved (i.e. fatal error) + # opcodes are not allowed to be empty + if len(op) == 0 or op[:2] == b"\xff\xff": + raise EvalError("reserved operator", args.to(op)) + + # all other unknown opcodes are no-ops + # the cost of the no-ops is determined by the opcode number, except the + # 6 least significant bits. + + cost_function = (op[-1] & 0b11000000) >> 6 + # the multiplier cannot be 0. it starts at 1 + + if len(op) > 5: + raise EvalError("invalid operator", args.to(op)) + + cost_multiplier = int.from_bytes(op[:-1], "big", signed=False) + 1 + + # 0 = constant + # 1 = like op_add/op_sub + # 2 = like op_multiply + # 3 = like op_concat + if cost_function == 0: + cost = 1 + elif cost_function == 1: + # like op_add + cost = ARITH_BASE_COST + arg_size = 0 + for length in args_len("unknown op", args): + arg_size += length + cost += ARITH_COST_PER_ARG + cost += arg_size * ARITH_COST_PER_BYTE + elif cost_function == 2: + # like op_multiply + cost = MUL_BASE_COST + operands = args_len("unknown op", args) + try: + vs = next(operands) + for rs in operands: + cost += MUL_COST_PER_OP + cost += (rs + vs) * MUL_LINEAR_COST_PER_BYTE + cost += (rs * vs) // MUL_SQUARE_COST_PER_BYTE_DIVIDER + # this is an estimate, since we don't want to actually multiply the + # values + vs += rs + except StopIteration: + pass + + elif cost_function == 3: + # like concat + cost = CONCAT_BASE_COST + length = 0 + for arg in args.as_iter(): + if arg.pair: + raise EvalError("unknown op on list", arg) + cost += CONCAT_COST_PER_ARG + length += len(arg.atom) + cost += length * CONCAT_COST_PER_BYTE + + cost *= cost_multiplier + if cost >= 2**32: + raise EvalError("invalid operator", args.to(op)) + + return (cost, args.to(b"")) diff --git a/clvm/operators.py b/clvm/operators.py index a63e6a88..f51bf5bc 100644 --- a/clvm/operators.py +++ b/clvm/operators.py @@ -3,11 +3,9 @@ from . import core_ops, more_ops from .CLVMObject import CLVMObject -from .SExp import SExp -from .EvalError import EvalError - from .casts import int_to_bytes from .op_utils import operators_for_module +from .handle_unknown_op import handle_unknown_op_softfork_ready as default_unknown_op from .costs import ( ARITH_BASE_COST, @@ -65,104 +63,20 @@ } -def args_len(op_name, args): - for arg in args.as_iter(): - if arg.pair: - raise EvalError("%s requires int args" % op_name, arg) - yield len(arg.as_atom()) - - -# unknown ops are reserved if they start with 0xffff -# otherwise, unknown ops are no-ops, but they have costs. The cost is computed -# like this: - -# byte index (reverse): -# | 4 | 3 | 2 | 1 | 0 | -# +---+---+---+---+------------+ -# | multiplier |XX | XXXXXX | -# +---+---+---+---+---+--------+ -# ^ ^ ^ -# | | + 6 bits ignored when computing cost -# cost_multiplier | -# + 2 bits -# cost_function - -# 1 is always added to the multiplier before using it to multiply the cost, this -# is since cost may not be 0. - -# cost_function is 2 bits and defines how cost is computed based on arguments: -# 0: constant, cost is 1 * (multiplier + 1) -# 1: computed like operator add, multiplied by (multiplier + 1) -# 2: computed like operator mul, multiplied by (multiplier + 1) -# 3: computed like operator concat, multiplied by (multiplier + 1) - -# this means that unknown ops where cost_function is 1, 2, or 3, may still be -# fatal errors if the arguments passed are not atoms. - -def default_unknown_op(op: bytes, args: CLVMObject) -> Tuple[int, CLVMObject]: - # any opcode starting with ffff is reserved (i.e. fatal error) - # opcodes are not allowed to be empty - if len(op) == 0 or op[:2] == b"\xff\xff": - raise EvalError("reserved operator", args.to(op)) - - # all other unknown opcodes are no-ops - # the cost of the no-ops is determined by the opcode number, except the - # 6 least significant bits. - - cost_function = (op[-1] & 0b11000000) >> 6 - # the multiplier cannot be 0. it starts at 1 - - if len(op) > 5: - raise EvalError("invalid operator", args.to(op)) - - cost_multiplier = int.from_bytes(op[:-1], "big", signed=False) + 1 - - # 0 = constant - # 1 = like op_add/op_sub - # 2 = like op_multiply - # 3 = like op_concat - if cost_function == 0: - cost = 1 - elif cost_function == 1: - # like op_add - cost = ARITH_BASE_COST - arg_size = 0 - for length in args_len("unknown op", args): - arg_size += length - cost += ARITH_COST_PER_ARG - cost += arg_size * ARITH_COST_PER_BYTE - elif cost_function == 2: - # like op_multiply - cost = MUL_BASE_COST - operands = args_len("unknown op", args) - try: - vs = next(operands) - for rs in operands: - cost += MUL_COST_PER_OP - cost += (rs + vs) * MUL_LINEAR_COST_PER_BYTE - cost += (rs * vs) // MUL_SQUARE_COST_PER_BYTE_DIVIDER - # this is an estimate, since we don't want to actually multiply the - # values - vs += rs - except StopIteration: - pass - - elif cost_function == 3: - # like concat - cost = CONCAT_BASE_COST - length = 0 - for arg in args.as_iter(): - if arg.pair: - raise EvalError("unknown op on list", arg) - cost += CONCAT_COST_PER_ARG - length += len(arg.atom) - cost += length * CONCAT_COST_PER_BYTE - - cost *= cost_multiplier - if cost >= 2**32: - raise EvalError("invalid operator", args.to(op)) - - return (cost, SExp.null()) +def default_native_opcodes(): + if NativeOpLookup is None: + return None + + d = {} + for idx, name in KEYWORD_FROM_ATOM.items(): + # name = OP_REWRITE.get(name, name) + if name in ".qa": + continue + d[name] = idx + return d + + +## DEPRECATED below here class OperatorDict(dict): @@ -199,6 +113,8 @@ def __call__(self, op: bytes, arguments: CLVMObject) -> Tuple[int, CLVMObject]: APPLY_ATOM = KEYWORD_TO_ATOM["a"] OPERATOR_LOOKUP = OperatorDict( - operators_for_module(KEYWORD_TO_ATOM, core_ops, OP_REWRITE), quote=QUOTE_ATOM, apply=APPLY_ATOM + operators_for_module(KEYWORD_TO_ATOM, core_ops, OP_REWRITE), + quote=QUOTE_ATOM, + apply=APPLY_ATOM, ) OPERATOR_LOOKUP.update(operators_for_module(KEYWORD_TO_ATOM, more_ops, OP_REWRITE)) diff --git a/clvm/run_program.py b/clvm/run_program.py index 20f4b75c..a08313e6 100644 --- a/clvm/run_program.py +++ b/clvm/run_program.py @@ -11,10 +11,20 @@ PATH_LOOKUP_COST_PER_LEG, PATH_LOOKUP_COST_PER_ZERO_BYTE ) +from .operators import KEYWORD_FROM_ATOM, OP_REWRITE + +try: + from clvm_rs import py_run_program, NativeOpLookup +except ImportError: + py_run_program = None + +# py_run_program = None # the "Any" below should really be "OpStackType" but # recursive types aren't supported by mypy +MultiOpFn = Callable[[bytes, SExp, int], Tuple[int, SExp]] + OpCallable = Callable[[Any, "ValStackType"], int] ValStackType = List[SExp] @@ -53,6 +63,54 @@ def run_program( pre_eval_f=None, ) -> Tuple[int, CLVMObject]: + if py_run_program: + + def unknown_op_callback(op, sexp): + s = SExp.to(sexp) + cost, r = operator_lookup(op, s) + r = SExp.to(r) + return cost, r + + default_native_opcodes = dict( + ("op_%s" % OP_REWRITE.get(k, k), op) + for op, k in KEYWORD_FROM_ATOM.items() + if k not in "qa." + ) + op_lookup = NativeOpLookup(default_native_opcodes, unknown_op_callback) + + cost, r = py_run_program( + program, + args, + operator_lookup["quote"][0], + operator_lookup["apply"][0], + max_cost or 0, + op_lookup, + pre_eval=pre_eval_f, + ) + r = SExp.to(r) + return cost, r + + return _run_program( + program, + args, + operator_lookup, + operator_lookup.quote_atom, + operator_lookup.apply_atom, + max_cost, + pre_eval_f, + ) + + +def _run_program( + program: CLVMObject, + args: CLVMObject, + operator_lookup: MultiOpFn, + quote_atom: bytes, + apply_atom: bytes, + max_cost=None, + pre_eval_f=None, +) -> Tuple[int, CLVMObject]: + program = SExp.to(program) if pre_eval_f: pre_eval_op = to_pre_eval_op(pre_eval_f, program.to) @@ -137,7 +195,7 @@ def eval_op(op_stack: OpStackType, value_stack: ValStackType) -> int: op = operator.as_atom() operand_list = sexp.rest() - if op == operator_lookup.quote_atom: + if op == quote_atom: value_stack.append(operand_list) return QUOTE_COST @@ -160,7 +218,7 @@ def apply_op(op_stack: OpStackType, value_stack: ValStackType) -> int: raise EvalError("internal error", operator) op = operator.as_atom() - if op == operator_lookup.apply_atom: + if op == apply_atom: if operand_list.list_len() != 2: raise EvalError("apply requires exactly 2 parameters", operand_list) new_program = operand_list.first() diff --git a/tests/operatordict_test.py b/tests/operatordict_test.py deleted file mode 100644 index 897f6ffe..00000000 --- a/tests/operatordict_test.py +++ /dev/null @@ -1,29 +0,0 @@ -import unittest - -from clvm.operators import OperatorDict - - -class OperatorDictTest(unittest.TestCase): - def test_operatordict_constructor(self): - """Constructing should fail if quote or apply are not specified, - either by object property or by keyword argument. - Note that they cannot be specified in the operator dictionary itself. - """ - d = {1: "hello", 2: "goodbye"} - with self.assertRaises(AttributeError): - o = OperatorDict(d) - with self.assertRaises(AttributeError): - o = OperatorDict(d, apply=1) - with self.assertRaises(AttributeError): - o = OperatorDict(d, quote=1) - o = OperatorDict(d, apply=1, quote=2) - print(o) - # Why does the constructed Operator dict contain entries for "apply":1 and "quote":2 ? - # assert d == o - self.assertEqual(o.apply_atom, 1) - self.assertEqual(o.quote_atom, 2) - - # Test construction from an already existing OperatorDict - o2 = OperatorDict(o) - self.assertEqual(o2.apply_atom, 1) - self.assertEqual(o2.quote_atom, 2) diff --git a/tests/operators_test.py b/tests/operators_test.py index 9c84d719..aa170981 100644 --- a/tests/operators_test.py +++ b/tests/operators_test.py @@ -1,59 +1,64 @@ import unittest -from clvm.operators import (OPERATOR_LOOKUP, KEYWORD_TO_ATOM, default_unknown_op, OperatorDict) +from clvm.chia_dialect import chia_dialect_info +from clvm.handle_unknown_op import handle_unknown_op_softfork_ready +from clvm.dialect import ChainableMultiOpFn +from clvm.operators import KEYWORD_TO_ATOM from clvm.EvalError import EvalError from clvm import SExp from clvm.costs import CONCAT_BASE_COST +OPERATOR_LOOKUP = ChainableMultiOpFn(chia_dialect_info().opcode_lookup, handle_unknown_op_softfork_ready) + class OperatorsTest(unittest.TestCase): def setUp(self): self.handler_called = False - def unknown_handler(self, name, args): + def unknown_handler(self, name, args, _max_cost): self.handler_called = True self.assertEqual(name, b'\xff\xff1337') self.assertEqual(args, SExp.to(1337)) return 42, SExp.to(b'foobar') def test_unknown_op(self): - self.assertRaises(EvalError, lambda: OPERATOR_LOOKUP(b'\xff\xff1337', SExp.to(1337))) - od = OperatorDict(OPERATOR_LOOKUP, unknown_op_handler=lambda name, args: self.unknown_handler(name, args)) - cost, ret = od(b'\xff\xff1337', SExp.to(1337)) + self.assertRaises(EvalError, lambda: OPERATOR_LOOKUP(b'\xff\xff1337', SExp.to(1337), None)) + od = ChainableMultiOpFn(chia_dialect_info().opcode_lookup, self.unknown_handler) + cost, ret = od(b'\xff\xff1337', SExp.to(1337), None) self.assertTrue(self.handler_called) self.assertEqual(cost, 42) self.assertEqual(ret, SExp.to(b'foobar')) def test_plus(self): print(OPERATOR_LOOKUP) - self.assertEqual(OPERATOR_LOOKUP(KEYWORD_TO_ATOM['+'], SExp.to([3, 4, 5]))[1], SExp.to(12)) + self.assertEqual(OPERATOR_LOOKUP(KEYWORD_TO_ATOM['+'], SExp.to([3, 4, 5]), None)[1], SExp.to(12)) def test_unknown_op_reserved(self): # any op that starts with ffff is reserved, and results in a hard # failure with self.assertRaises(EvalError): - default_unknown_op(b"\xff\xff", SExp.null()) + handle_unknown_op_softfork_ready(b"\xff\xff", SExp.null()) for suffix in [b"\xff", b"0", b"\x00", b"\xcc\xcc\xfe\xed\xfa\xce"]: with self.assertRaises(EvalError): - default_unknown_op(b"\xff\xff" + suffix, SExp.null()) + handle_unknown_op_softfork_ready(b"\xff\xff" + suffix, SExp.null()) with self.assertRaises(EvalError): # an empty atom is not a valid opcode - self.assertEqual(default_unknown_op(b"", SExp.null()), (1, SExp.null())) + self.assertEqual(handle_unknown_op_softfork_ready(b"", SExp.null()), (1, SExp.null())) # a single ff is not sufficient to be treated as a reserved opcode - self.assertEqual(default_unknown_op(b"\xff", SExp.null()), (CONCAT_BASE_COST, SExp.null())) + self.assertEqual(handle_unknown_op_softfork_ready(b"\xff", SExp.null()), (CONCAT_BASE_COST, SExp.null())) # leading zeroes count, and this does not count as a ffff-prefix # the cost is 0xffff00 = 16776960 - self.assertEqual(default_unknown_op(b"\x00\xff\xff\x00\x00", SExp.null()), (16776961, SExp.null())) + self.assertEqual(handle_unknown_op_softfork_ready(b"\x00\xff\xff\x00\x00", SExp.null()), (16776961, SExp.null())) def test_unknown_ops_last_bits(self): # The last byte is ignored for no-op unknown ops for suffix in [b"\x3f", b"\x0f", b"\x00", b"\x2c"]: # the cost is unchanged by the last byte - self.assertEqual(default_unknown_op(b"\x3c" + suffix, SExp.null()), (61, SExp.null())) + self.assertEqual(handle_unknown_op_softfork_ready(b"\x3c" + suffix, SExp.null()), (61, SExp.null())) From 6a1c1724ba01650252704e7245e3e754c649b8a8 Mon Sep 17 00:00:00 2001 From: Richard Kiss Date: Fri, 7 May 2021 16:41:29 -0700 Subject: [PATCH 02/11] Improvements --- clvm/chia_dialect.py | 6 +++ clvm/dialect.py | 10 +++-- clvm/handle_unknown_op.py | 8 ++-- clvm/operators.py | 84 +++++---------------------------------- clvm/run_program.py | 35 ---------------- tests/operators_test.py | 50 ++++++++++++++++------- 6 files changed, 62 insertions(+), 131 deletions(-) diff --git a/clvm/chia_dialect.py b/clvm/chia_dialect.py index 7acdaeae..0653ea0d 100644 --- a/clvm/chia_dialect.py +++ b/clvm/chia_dialect.py @@ -8,16 +8,22 @@ KEYWORDS = ( # core opcodes 0x01-x08 ". q a i c f r l x " + # opcodes on atoms as strings 0x09-0x0f "= >s sha256 substr strlen concat . " + # opcodes on atoms as ints 0x10-0x17 "+ - * / divmod > ash lsh " + # opcodes on atoms as vectors of bools 0x18-0x1c "logand logior logxor lognot . " + # opcodes for bls 1381 0x1d-0x1f "point_add pubkey_for_exp . " + # bool opcodes 0x20-0x23 "not any all . " + # misc 0x24 "softfork " ).split() diff --git a/clvm/dialect.py b/clvm/dialect.py index 57dceee2..7e0a3f7f 100644 --- a/clvm/dialect.py +++ b/clvm/dialect.py @@ -22,7 +22,9 @@ class ChainableMultiOpFn(MultiOpFn): op_lookup: OperatorDict unknown_op_handler: MultiOpFn - def __call__(self, op: bytes, arguments: CLVMObjectType, max_cost: int) -> Tuple[int, CLVMObjectType]: + def __call__( + self, op: bytes, arguments: CLVMObjectType, max_cost: Optional[int] = None + ) -> Tuple[int, CLVMObjectType]: f = self.op_lookup.get(op) if f: try: @@ -47,7 +49,9 @@ def __init__( unknown_callback: MultiOpFn, ): self.dialect_info = dialect_info - self.multi_op_fn = ChainableMultiOpFn(self.dialect_info.opcode_lookup, unknown_callback) + self.multi_op_fn = ChainableMultiOpFn( + self.dialect_info.opcode_lookup, unknown_callback + ) def run_program( self, @@ -65,5 +69,5 @@ def run_program( self.dialect_info.quote_kw, self.dialect_info.apply_kw, max_cost, - pre_eval_f + pre_eval_f, ) diff --git a/clvm/handle_unknown_op.py b/clvm/handle_unknown_op.py index c1250a43..68a10629 100644 --- a/clvm/handle_unknown_op.py +++ b/clvm/handle_unknown_op.py @@ -5,15 +5,15 @@ from .costs import ( ARITH_BASE_COST, - ARITH_COST_PER_LIMB_DIVIDER, + ARITH_COST_PER_BYTE, ARITH_COST_PER_ARG, MUL_BASE_COST, MUL_COST_PER_OP, - MUL_LINEAR_COST_PER_BYTE_DIVIDER, + MUL_LINEAR_COST_PER_BYTE, MUL_SQUARE_COST_PER_BYTE_DIVIDER, CONCAT_BASE_COST, CONCAT_COST_PER_ARG, - CONCAT_COST_PER_BYTE_DIVIDER, + CONCAT_COST_PER_BYTE, ) @@ -55,7 +55,7 @@ def args_len(op_name, args): # this means that unknown ops where cost_function is 1, 2, or 3, may still be # fatal errors if the arguments passed are not atoms. -def handle_unknown_op_softfork_ready(op: bytes, args: CLVMObject) -> Tuple[int, CLVMObject]: +def handle_unknown_op_softfork_ready(op: bytes, args: CLVMObject, max_cost: int) -> Tuple[int, CLVMObject]: # any opcode starting with ffff is reserved (i.e. fatal error) # opcodes are not allowed to be empty if len(op) == 0 or op[:2] == b"\xff\xff": diff --git a/clvm/operators.py b/clvm/operators.py index f51bf5bc..82cb2cd4 100644 --- a/clvm/operators.py +++ b/clvm/operators.py @@ -1,82 +1,13 @@ +# this API is deprecated in favor of dialects. See `dialect.py` and `chia_dialect.py` + from typing import Dict, Tuple from . import core_ops, more_ops from .CLVMObject import CLVMObject -from .casts import int_to_bytes from .op_utils import operators_for_module -from .handle_unknown_op import handle_unknown_op_softfork_ready as default_unknown_op - -from .costs import ( - ARITH_BASE_COST, - ARITH_COST_PER_BYTE, - ARITH_COST_PER_ARG, - MUL_BASE_COST, - MUL_COST_PER_OP, - MUL_LINEAR_COST_PER_BYTE, - MUL_SQUARE_COST_PER_BYTE_DIVIDER, - CONCAT_BASE_COST, - CONCAT_COST_PER_ARG, - CONCAT_COST_PER_BYTE, -) - -KEYWORDS = ( - # core opcodes 0x01-x08 - ". q a i c f r l x " - - # opcodes on atoms as strings 0x09-0x0f - "= >s sha256 substr strlen concat . " - - # opcodes on atoms as ints 0x10-0x17 - "+ - * / divmod > ash lsh " - - # opcodes on atoms as vectors of bools 0x18-0x1c - "logand logior logxor lognot . " - - # opcodes for bls 1381 0x1d-0x1f - "point_add pubkey_for_exp . " - - # bool opcodes 0x20-0x23 - "not any all . " - - # misc 0x24 - "softfork " -).split() - -KEYWORD_FROM_ATOM = {int_to_bytes(k): v for k, v in enumerate(KEYWORDS)} -KEYWORD_TO_ATOM = {v: k for k, v in KEYWORD_FROM_ATOM.items()} - -OP_REWRITE = { - "+": "add", - "-": "subtract", - "*": "multiply", - "/": "div", - "i": "if", - "c": "cons", - "f": "first", - "r": "rest", - "l": "listp", - "x": "raise", - "=": "eq", - ">": "gr", - ">s": "gr_bytes", -} - - -def default_native_opcodes(): - if NativeOpLookup is None: - return None - - d = {} - for idx, name in KEYWORD_FROM_ATOM.items(): - # name = OP_REWRITE.get(name, name) - if name in ".qa": - continue - d[name] = idx - return d - - -## DEPRECATED below here +from .handle_unknown_op import handle_unknown_op_softfork_ready +from .chia_dialect import KEYWORDS, OP_REWRITE, KEYWORD_FROM_ATOM, KEYWORD_TO_ATOM # noqa class OperatorDict(dict): @@ -98,13 +29,16 @@ def __new__(class_, d: Dict, *args, **kwargs): if "unknown_op_handler" in kwargs: self.unknown_op_handler = kwargs["unknown_op_handler"] else: - self.unknown_op_handler = default_unknown_op + self.unknown_op_handler = handle_unknown_op_softfork_ready return self def __call__(self, op: bytes, arguments: CLVMObject) -> Tuple[int, CLVMObject]: f = self.get(op) if f is None: - return self.unknown_op_handler(op, arguments) + try: + return self.unknown_op_handler(op, arguments, max_cost=None) + except TypeError: + return self.unknown_op_handler(op, arguments) else: return f(arguments) diff --git a/clvm/run_program.py b/clvm/run_program.py index a08313e6..7b7fcb17 100644 --- a/clvm/run_program.py +++ b/clvm/run_program.py @@ -11,14 +11,6 @@ PATH_LOOKUP_COST_PER_LEG, PATH_LOOKUP_COST_PER_ZERO_BYTE ) -from .operators import KEYWORD_FROM_ATOM, OP_REWRITE - -try: - from clvm_rs import py_run_program, NativeOpLookup -except ImportError: - py_run_program = None - -# py_run_program = None # the "Any" below should really be "OpStackType" but # recursive types aren't supported by mypy @@ -63,33 +55,6 @@ def run_program( pre_eval_f=None, ) -> Tuple[int, CLVMObject]: - if py_run_program: - - def unknown_op_callback(op, sexp): - s = SExp.to(sexp) - cost, r = operator_lookup(op, s) - r = SExp.to(r) - return cost, r - - default_native_opcodes = dict( - ("op_%s" % OP_REWRITE.get(k, k), op) - for op, k in KEYWORD_FROM_ATOM.items() - if k not in "qa." - ) - op_lookup = NativeOpLookup(default_native_opcodes, unknown_op_callback) - - cost, r = py_run_program( - program, - args, - operator_lookup["quote"][0], - operator_lookup["apply"][0], - max_cost or 0, - op_lookup, - pre_eval=pre_eval_f, - ) - r = SExp.to(r) - return cost, r - return _run_program( program, args, diff --git a/tests/operators_test.py b/tests/operators_test.py index aa170981..c8b8c8bb 100644 --- a/tests/operators_test.py +++ b/tests/operators_test.py @@ -8,57 +8,79 @@ from clvm import SExp from clvm.costs import CONCAT_BASE_COST -OPERATOR_LOOKUP = ChainableMultiOpFn(chia_dialect_info().opcode_lookup, handle_unknown_op_softfork_ready) +OPERATOR_LOOKUP = ChainableMultiOpFn( + chia_dialect_info().opcode_lookup, handle_unknown_op_softfork_ready +) class OperatorsTest(unittest.TestCase): - def setUp(self): self.handler_called = False def unknown_handler(self, name, args, _max_cost): self.handler_called = True - self.assertEqual(name, b'\xff\xff1337') + self.assertEqual(name, b"\xff\xff1337") self.assertEqual(args, SExp.to(1337)) - return 42, SExp.to(b'foobar') + return 42, SExp.to(b"foobar") def test_unknown_op(self): - self.assertRaises(EvalError, lambda: OPERATOR_LOOKUP(b'\xff\xff1337', SExp.to(1337), None)) + self.assertRaises( + EvalError, lambda: OPERATOR_LOOKUP(b"\xff\xff1337", SExp.to(1337), None) + ) od = ChainableMultiOpFn(chia_dialect_info().opcode_lookup, self.unknown_handler) - cost, ret = od(b'\xff\xff1337', SExp.to(1337), None) + cost, ret = od(b"\xff\xff1337", SExp.to(1337), None) self.assertTrue(self.handler_called) self.assertEqual(cost, 42) - self.assertEqual(ret, SExp.to(b'foobar')) + self.assertEqual(ret, SExp.to(b"foobar")) def test_plus(self): print(OPERATOR_LOOKUP) - self.assertEqual(OPERATOR_LOOKUP(KEYWORD_TO_ATOM['+'], SExp.to([3, 4, 5]), None)[1], SExp.to(12)) + self.assertEqual( + OPERATOR_LOOKUP(KEYWORD_TO_ATOM["+"], SExp.to([3, 4, 5]), None)[1], + SExp.to(12), + ) def test_unknown_op_reserved(self): # any op that starts with ffff is reserved, and results in a hard # failure with self.assertRaises(EvalError): - handle_unknown_op_softfork_ready(b"\xff\xff", SExp.null()) + handle_unknown_op_softfork_ready(b"\xff\xff", SExp.null(), max_cost=None) for suffix in [b"\xff", b"0", b"\x00", b"\xcc\xcc\xfe\xed\xfa\xce"]: with self.assertRaises(EvalError): - handle_unknown_op_softfork_ready(b"\xff\xff" + suffix, SExp.null()) + handle_unknown_op_softfork_ready( + b"\xff\xff" + suffix, SExp.null(), max_cost=None + ) with self.assertRaises(EvalError): # an empty atom is not a valid opcode - self.assertEqual(handle_unknown_op_softfork_ready(b"", SExp.null()), (1, SExp.null())) + self.assertEqual( + handle_unknown_op_softfork_ready(b"", SExp.null(), max_cost=None), + (1, SExp.null()), + ) # a single ff is not sufficient to be treated as a reserved opcode - self.assertEqual(handle_unknown_op_softfork_ready(b"\xff", SExp.null()), (CONCAT_BASE_COST, SExp.null())) + self.assertEqual( + handle_unknown_op_softfork_ready(b"\xff", SExp.null(), max_cost=None), + (CONCAT_BASE_COST, SExp.null()), + ) # leading zeroes count, and this does not count as a ffff-prefix # the cost is 0xffff00 = 16776960 - self.assertEqual(handle_unknown_op_softfork_ready(b"\x00\xff\xff\x00\x00", SExp.null()), (16776961, SExp.null())) + self.assertEqual( + handle_unknown_op_softfork_ready( + b"\x00\xff\xff\x00\x00", SExp.null(), max_cost=None + ), + (16776961, SExp.null()), + ) def test_unknown_ops_last_bits(self): # The last byte is ignored for no-op unknown ops for suffix in [b"\x3f", b"\x0f", b"\x00", b"\x2c"]: # the cost is unchanged by the last byte - self.assertEqual(handle_unknown_op_softfork_ready(b"\x3c" + suffix, SExp.null()), (61, SExp.null())) + self.assertEqual( + handle_unknown_op_softfork_ready(b"\x3c" + suffix, SExp.null(), max_cost=None), + (61, SExp.null()), + ) From 29b2b2fdf43b77a4c25efb79be04d58bb9ca2353 Mon Sep 17 00:00:00 2001 From: Richard Kiss Date: Sat, 8 May 2021 10:05:40 -0700 Subject: [PATCH 03/11] Tweak dialect api. --- clvm/dialect.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/clvm/dialect.py b/clvm/dialect.py index 7e0a3f7f..3362ff47 100644 --- a/clvm/dialect.py +++ b/clvm/dialect.py @@ -18,7 +18,7 @@ @dataclass -class ChainableMultiOpFn(MultiOpFn): +class ChainableMultiOpFn: op_lookup: OperatorDict unknown_op_handler: MultiOpFn @@ -45,13 +45,13 @@ class DialectInfo: class Dialect: def __init__( self, - dialect_info: DialectInfo, - unknown_callback: MultiOpFn, + quote_kw: bytes, + apply_kw: bytes, + multi_op_fn: MultiOpFn, ): - self.dialect_info = dialect_info - self.multi_op_fn = ChainableMultiOpFn( - self.dialect_info.opcode_lookup, unknown_callback - ) + self.quote_kw = quote_kw + self.apply_kw = apply_kw + self.multi_op_fn = multi_op_fn def run_program( self, @@ -66,8 +66,8 @@ def run_program( program, env, self.multi_op_fn, - self.dialect_info.quote_kw, - self.dialect_info.apply_kw, + self.quote_kw, + self.apply_kw, max_cost, pre_eval_f, ) From 788b2d44ddf7dbce0d58937ee4f26985b8542e36 Mon Sep 17 00:00:00 2001 From: Richard Kiss Date: Mon, 10 May 2021 13:39:55 -0700 Subject: [PATCH 04/11] Tweak `Dialect` so it works with rust version. --- clvm/chia_dialect.py | 66 ++++++++++++++++++++++++++++++++++----- clvm/dialect.py | 3 +- clvm/handle_unknown_op.py | 5 ++- 3 files changed, 65 insertions(+), 9 deletions(-) diff --git a/clvm/chia_dialect.py b/clvm/chia_dialect.py index 0653ea0d..d26f412e 100644 --- a/clvm/chia_dialect.py +++ b/clvm/chia_dialect.py @@ -1,9 +1,16 @@ +try: + from clvm_rs import native_opcodes_dict, Dialect as NativeDialect +except ImportError: + NativeDialect = native_opcodes_dict = None + from . import core_ops, more_ops from .casts import int_to_bytes -from .dialect import DialectInfo -from .op_utils import operators_for_module - +from .dialect import Dialect, DialectInfo +from .handle_unknown_op import ( + handle_unknown_op_softfork_ready, + handle_unknown_op_strict, +) KEYWORDS = ( # core opcodes 0x01-x08 @@ -48,7 +55,52 @@ } -def chia_dialect_info(): - op_lookup = operators_for_module(KEYWORD_TO_ATOM, core_ops, OP_REWRITE) - op_lookup.update(operators_for_module(KEYWORD_TO_ATOM, more_ops, OP_REWRITE)) - return DialectInfo(KEYWORD_TO_ATOM["q"], KEYWORD_TO_ATOM["a"], op_lookup) +def op_table_for_module(mod): + return {k: v for k, v in mod.__dict__.items() if k.startswith("op_")} + + +def chia_operator_table(): + if native_opcodes_dict: + return native_opcodes_dict() + table = {} + table.update(op_table_for_module(core_ops)) + table.update(op_table_for_module(more_ops)) + return table + + +def chia_dialect_info(keyword_to_atom, op_rewrite): + op_table = chia_operator_table() + op_lookup = {} + for op, bytecode in keyword_to_atom.items(): + if op in "qa.": + continue + op_name = "op_%s" % op_rewrite.get(op, op) + op_f = op_table[op_name] + op_lookup[bytecode] = op_f + return DialectInfo(keyword_to_atom["q"], keyword_to_atom["a"], op_lookup) + + +def chia_dialect_with_op_table(strict=True): + dialect_info = chia_dialect_info(KEYWORD_TO_ATOM, OP_REWRITE) + unknown_op_callback = ( + handle_unknown_op_strict if strict else handle_unknown_op_softfork_ready + ) + if NativeDialect: + dialect = NativeDialect( + dialect_info.quote_kw, + dialect_info.apply_kw, + dialect_info.opcode_lookup, + unknown_op_callback, + ) + else: + dialect = Dialect( + dialect_info.quote_kw, + dialect_info.apply_kw, + dialect_info.opcode_lookup, + unknown_op_callback, + ) + return dialect, dialect_info.opcode_lookup + + +def chia_dialect(strict): + return chia_dialect_with_op_table(strict)[0] diff --git a/clvm/dialect.py b/clvm/dialect.py index 3362ff47..f009715e 100644 --- a/clvm/dialect.py +++ b/clvm/dialect.py @@ -47,11 +47,12 @@ def __init__( self, quote_kw: bytes, apply_kw: bytes, + opcode_lookup: OperatorDict, multi_op_fn: MultiOpFn, ): self.quote_kw = quote_kw self.apply_kw = apply_kw - self.multi_op_fn = multi_op_fn + self.multi_op_fn = ChainableMultiOpFn(opcode_lookup, multi_op_fn) def run_program( self, diff --git a/clvm/handle_unknown_op.py b/clvm/handle_unknown_op.py index 68a10629..d30211b1 100644 --- a/clvm/handle_unknown_op.py +++ b/clvm/handle_unknown_op.py @@ -55,7 +55,10 @@ def args_len(op_name, args): # this means that unknown ops where cost_function is 1, 2, or 3, may still be # fatal errors if the arguments passed are not atoms. -def handle_unknown_op_softfork_ready(op: bytes, args: CLVMObject, max_cost: int) -> Tuple[int, CLVMObject]: + +def handle_unknown_op_softfork_ready( + op: bytes, args: CLVMObject, max_cost: int +) -> Tuple[int, CLVMObject]: # any opcode starting with ffff is reserved (i.e. fatal error) # opcodes are not allowed to be empty if len(op) == 0 or op[:2] == b"\xff\xff": From 76a5562fcf46d1dbd0bcf6bb2437369beeefa6c8 Mon Sep 17 00:00:00 2001 From: Richard Kiss Date: Mon, 10 May 2021 17:41:04 -0700 Subject: [PATCH 05/11] All tests passing with python backend. --- clvm/chia_dialect.py | 16 ++++++++++++---- clvm/serialize.py | 4 ++-- tests/brun/trace-1.txt | 2 +- tests/brun/trace-2.txt | 2 +- tests/operators_test.py | 9 +++++---- 5 files changed, 21 insertions(+), 12 deletions(-) diff --git a/clvm/chia_dialect.py b/clvm/chia_dialect.py index d26f412e..1e5c0151 100644 --- a/clvm/chia_dialect.py +++ b/clvm/chia_dialect.py @@ -1,5 +1,10 @@ try: - from clvm_rs import native_opcodes_dict, Dialect as NativeDialect + from clvm_rs import ( + native_opcodes_dict, + Dialect as NativeDialect, + NATIVE_OP_UNKNOWN_NON_STRICT, + NATIVE_OP_UNKNOWN_STRICT, + ) except ImportError: NativeDialect = native_opcodes_dict = None @@ -82,10 +87,10 @@ def chia_dialect_info(keyword_to_atom, op_rewrite): def chia_dialect_with_op_table(strict=True): dialect_info = chia_dialect_info(KEYWORD_TO_ATOM, OP_REWRITE) - unknown_op_callback = ( - handle_unknown_op_strict if strict else handle_unknown_op_softfork_ready - ) if NativeDialect: + unknown_op_callback = ( + NATIVE_OP_UNKNOWN_STRICT if strict else NATIVE_OP_UNKNOWN_NON_STRICT + ) dialect = NativeDialect( dialect_info.quote_kw, dialect_info.apply_kw, @@ -93,6 +98,9 @@ def chia_dialect_with_op_table(strict=True): unknown_op_callback, ) else: + unknown_op_callback = ( + handle_unknown_op_strict if strict else handle_unknown_op_softfork_ready + ) dialect = Dialect( dialect_info.quote_kw, dialect_info.apply_kw, diff --git a/clvm/serialize.py b/clvm/serialize.py index d45ed75a..accda2f1 100644 --- a/clvm/serialize.py +++ b/clvm/serialize.py @@ -22,13 +22,13 @@ def sexp_to_byte_iterator(sexp): todo_stack = [sexp] while todo_stack: sexp = todo_stack.pop() - pair = sexp.as_pair() + pair = sexp.pair if pair: yield bytes([CONS_BOX_MARKER]) todo_stack.append(pair[1]) todo_stack.append(pair[0]) else: - yield from atom_to_byte_iterator(sexp.as_atom()) + yield from atom_to_byte_iterator(sexp.atom) def atom_to_byte_iterator(as_atom): diff --git a/tests/brun/trace-1.txt b/tests/brun/trace-1.txt index 72fb303f..a1d5c92d 100644 --- a/tests/brun/trace-1.txt +++ b/tests/brun/trace-1.txt @@ -1,4 +1,4 @@ -brun --backend=python -c -v '(+ (q . 10) (f 1))' '(51)' +brun -c -v '(+ (q . 10) (f 1))' '(51)' cost = 860 61 diff --git a/tests/brun/trace-2.txt b/tests/brun/trace-2.txt index fb475ec5..08ba2687 100644 --- a/tests/brun/trace-2.txt +++ b/tests/brun/trace-2.txt @@ -1,4 +1,4 @@ -brun --backend=python -c -v '(x)' +brun -c -v '(x)' FAIL: clvm raise () (a 2 3) [((x))] => (didn't finish) diff --git a/tests/operators_test.py b/tests/operators_test.py index c8b8c8bb..137a1991 100644 --- a/tests/operators_test.py +++ b/tests/operators_test.py @@ -3,14 +3,15 @@ from clvm.chia_dialect import chia_dialect_info from clvm.handle_unknown_op import handle_unknown_op_softfork_ready from clvm.dialect import ChainableMultiOpFn -from clvm.operators import KEYWORD_TO_ATOM +from clvm.operators import KEYWORD_TO_ATOM, OP_REWRITE from clvm.EvalError import EvalError from clvm import SExp from clvm.costs import CONCAT_BASE_COST OPERATOR_LOOKUP = ChainableMultiOpFn( - chia_dialect_info().opcode_lookup, handle_unknown_op_softfork_ready + chia_dialect_info(KEYWORD_TO_ATOM, OP_REWRITE).opcode_lookup, handle_unknown_op_softfork_ready ) +MAX_COST = int(1e18) class OperatorsTest(unittest.TestCase): @@ -27,7 +28,7 @@ def test_unknown_op(self): self.assertRaises( EvalError, lambda: OPERATOR_LOOKUP(b"\xff\xff1337", SExp.to(1337), None) ) - od = ChainableMultiOpFn(chia_dialect_info().opcode_lookup, self.unknown_handler) + od = ChainableMultiOpFn(chia_dialect_info(KEYWORD_TO_ATOM, OP_REWRITE).opcode_lookup, self.unknown_handler) cost, ret = od(b"\xff\xff1337", SExp.to(1337), None) self.assertTrue(self.handler_called) self.assertEqual(cost, 42) @@ -36,7 +37,7 @@ def test_unknown_op(self): def test_plus(self): print(OPERATOR_LOOKUP) self.assertEqual( - OPERATOR_LOOKUP(KEYWORD_TO_ATOM["+"], SExp.to([3, 4, 5]), None)[1], + OPERATOR_LOOKUP(KEYWORD_TO_ATOM["+"], SExp.to([3, 4, 5]), MAX_COST)[1], SExp.to(12), ) From 5f87760d6f510d8c32b705320f22c6aad2f28058 Mon Sep 17 00:00:00 2001 From: Richard Kiss Date: Fri, 14 May 2021 15:04:36 -0700 Subject: [PATCH 06/11] Add `to_python` and remove `DialectInfo`. --- clvm/chia_dialect.py | 31 +++++++++++++++---------------- clvm/dialect.py | 24 ++++++++++++++---------- tests/operators_test.py | 12 +++++------- 3 files changed, 34 insertions(+), 33 deletions(-) diff --git a/clvm/chia_dialect.py b/clvm/chia_dialect.py index 1e5c0151..a95a5dd1 100644 --- a/clvm/chia_dialect.py +++ b/clvm/chia_dialect.py @@ -11,7 +11,7 @@ from . import core_ops, more_ops from .casts import int_to_bytes -from .dialect import Dialect, DialectInfo +from .dialect import ConversionFn, Dialect from .handle_unknown_op import ( handle_unknown_op_softfork_ready, handle_unknown_op_strict, @@ -73,7 +73,7 @@ def chia_operator_table(): return table -def chia_dialect_info(keyword_to_atom, op_rewrite): +def chia_dialect_op_lookup(keyword_to_atom, op_rewrite): op_table = chia_operator_table() op_lookup = {} for op, bytecode in keyword_to_atom.items(): @@ -82,33 +82,32 @@ def chia_dialect_info(keyword_to_atom, op_rewrite): op_name = "op_%s" % op_rewrite.get(op, op) op_f = op_table[op_name] op_lookup[bytecode] = op_f - return DialectInfo(keyword_to_atom["q"], keyword_to_atom["a"], op_lookup) + return op_lookup -def chia_dialect_with_op_table(strict=True): - dialect_info = chia_dialect_info(KEYWORD_TO_ATOM, OP_REWRITE) +def chia_dialect(strict: bool, to_python: ConversionFn) -> Dialect: + quote_kw = KEYWORD_TO_ATOM["q"] + apply_kw = KEYWORD_TO_ATOM["a"] + opcode_lookup = chia_dialect_op_lookup(KEYWORD_TO_ATOM, OP_REWRITE) if NativeDialect: unknown_op_callback = ( NATIVE_OP_UNKNOWN_STRICT if strict else NATIVE_OP_UNKNOWN_NON_STRICT ) dialect = NativeDialect( - dialect_info.quote_kw, - dialect_info.apply_kw, - dialect_info.opcode_lookup, + quote_kw, + apply_kw, unknown_op_callback, + to_python=to_python, ) else: unknown_op_callback = ( handle_unknown_op_strict if strict else handle_unknown_op_softfork_ready ) dialect = Dialect( - dialect_info.quote_kw, - dialect_info.apply_kw, - dialect_info.opcode_lookup, + quote_kw, + apply_kw, unknown_op_callback, + to_python=to_python, ) - return dialect, dialect_info.opcode_lookup - - -def chia_dialect(strict): - return chia_dialect_with_op_table(strict)[0] + dialect.update(opcode_lookup) + return dialect diff --git a/clvm/dialect.py b/clvm/dialect.py index f009715e..084f6d7a 100644 --- a/clvm/dialect.py +++ b/clvm/dialect.py @@ -12,6 +12,8 @@ MultiOpFn = Callable[[bytes, CLVMObjectType, int], Tuple[int, CLVMObjectType]] +ConversionFn = Callable[[CLVMObjectType], CLVMObjectType] + OpFn = Callable[[CLVMObjectType, int], Tuple[int, CLVMObjectType]] OperatorDict = Dict[bytes, Callable[[CLVMObjectType, int], Tuple[int, CLVMObjectType]]] @@ -35,24 +37,25 @@ def __call__( return self.unknown_op_handler(op, arguments, max_cost) -@dataclass -class DialectInfo: - quote_kw: bytes - apply_kw: bytes - opcode_lookup: OperatorDict - - class Dialect: def __init__( self, quote_kw: bytes, apply_kw: bytes, - opcode_lookup: OperatorDict, multi_op_fn: MultiOpFn, + to_python: ConversionFn, ): self.quote_kw = quote_kw self.apply_kw = apply_kw - self.multi_op_fn = ChainableMultiOpFn(opcode_lookup, multi_op_fn) + self.opcode_lookup = dict() + self.multi_op_fn = ChainableMultiOpFn(self.opcode_lookup, multi_op_fn) + self.to_python = to_python + + def update(self, d: OperatorDict) -> None: + self.opcode_lookup.update(d) + + def clear(self) -> None: + self.opcode_lookup.clear() def run_program( self, @@ -63,7 +66,7 @@ def run_program( Callable[[CLVMObjectType, CLVMObjectType], Tuple[int, CLVMObjectType]] ] = None, ) -> Tuple[int, CLVMObjectType]: - return _run_program( + cost, r = _run_program( program, env, self.multi_op_fn, @@ -72,3 +75,4 @@ def run_program( max_cost, pre_eval_f, ) + return cost, self.to_python(r) diff --git a/tests/operators_test.py b/tests/operators_test.py index 137a1991..093b4c6b 100644 --- a/tests/operators_test.py +++ b/tests/operators_test.py @@ -1,6 +1,6 @@ import unittest -from clvm.chia_dialect import chia_dialect_info +from clvm.chia_dialect import chia_dialect_op_lookup from clvm.handle_unknown_op import handle_unknown_op_softfork_ready from clvm.dialect import ChainableMultiOpFn from clvm.operators import KEYWORD_TO_ATOM, OP_REWRITE @@ -8,9 +8,7 @@ from clvm import SExp from clvm.costs import CONCAT_BASE_COST -OPERATOR_LOOKUP = ChainableMultiOpFn( - chia_dialect_info(KEYWORD_TO_ATOM, OP_REWRITE).opcode_lookup, handle_unknown_op_softfork_ready -) +OPERATOR_LOOKUP = chia_dialect_op_lookup(KEYWORD_TO_ATOM, OP_REWRITE) MAX_COST = int(1e18) @@ -26,9 +24,9 @@ def unknown_handler(self, name, args, _max_cost): def test_unknown_op(self): self.assertRaises( - EvalError, lambda: OPERATOR_LOOKUP(b"\xff\xff1337", SExp.to(1337), None) + KeyError, lambda: OPERATOR_LOOKUP[b"\xff\xff1337"](SExp.to(1337), None) ) - od = ChainableMultiOpFn(chia_dialect_info(KEYWORD_TO_ATOM, OP_REWRITE).opcode_lookup, self.unknown_handler) + od = ChainableMultiOpFn(chia_dialect_op_lookup(KEYWORD_TO_ATOM, OP_REWRITE), self.unknown_handler) cost, ret = od(b"\xff\xff1337", SExp.to(1337), None) self.assertTrue(self.handler_called) self.assertEqual(cost, 42) @@ -37,7 +35,7 @@ def test_unknown_op(self): def test_plus(self): print(OPERATOR_LOOKUP) self.assertEqual( - OPERATOR_LOOKUP(KEYWORD_TO_ATOM["+"], SExp.to([3, 4, 5]), MAX_COST)[1], + OPERATOR_LOOKUP[KEYWORD_TO_ATOM["+"]](SExp.to([3, 4, 5]), MAX_COST)[1], SExp.to(12), ) From 204c312862eb4053d80808d12fc6aa60b764068c Mon Sep 17 00:00:00 2001 From: Richard Kiss Date: Fri, 14 May 2021 17:24:52 -0700 Subject: [PATCH 07/11] Improve `Dialect` stuff. --- clvm/chainable_multi_op_fn.py | 26 ++++++++ clvm/chia_dialect.py | 86 ++----------------------- clvm/dialect.py | 118 +++++++++++++++++++++++++++------- clvm/operators.py | 3 +- clvm/types.py | 15 +++++ tests/operators_test.py | 19 ++++-- 6 files changed, 153 insertions(+), 114 deletions(-) create mode 100644 clvm/chainable_multi_op_fn.py create mode 100644 clvm/types.py diff --git a/clvm/chainable_multi_op_fn.py b/clvm/chainable_multi_op_fn.py new file mode 100644 index 00000000..be25c8f4 --- /dev/null +++ b/clvm/chainable_multi_op_fn.py @@ -0,0 +1,26 @@ +from dataclasses import dataclass +from typing import Optional, Tuple + +from .types import CLVMObjectType, MultiOpFn, OperatorDict + + +@dataclass +class ChainableMultiOpFn: + """ + This structure handles clvm operators. Given an atom, it looks it up in a `dict`, then + falls back to calling `unknown_op_handler`. + """ + op_lookup: OperatorDict + unknown_op_handler: MultiOpFn + + def __call__( + self, op: bytes, arguments: CLVMObjectType, max_cost: Optional[int] = None + ) -> Tuple[int, CLVMObjectType]: + f = self.op_lookup.get(op) + if f: + try: + return f(arguments) + except TypeError: + # some operators require `max_cost` + return f(arguments, max_cost) + return self.unknown_op_handler(op, arguments, max_cost) diff --git a/clvm/chia_dialect.py b/clvm/chia_dialect.py index a95a5dd1..a35e4b21 100644 --- a/clvm/chia_dialect.py +++ b/clvm/chia_dialect.py @@ -1,21 +1,5 @@ -try: - from clvm_rs import ( - native_opcodes_dict, - Dialect as NativeDialect, - NATIVE_OP_UNKNOWN_NON_STRICT, - NATIVE_OP_UNKNOWN_STRICT, - ) -except ImportError: - NativeDialect = native_opcodes_dict = None - -from . import core_ops, more_ops - from .casts import int_to_bytes -from .dialect import ConversionFn, Dialect -from .handle_unknown_op import ( - handle_unknown_op_softfork_ready, - handle_unknown_op_strict, -) +from .dialect import ConversionFn, Dialect, new_dialect, opcode_table_for_backend KEYWORDS = ( # core opcodes 0x01-x08 @@ -43,71 +27,11 @@ KEYWORD_FROM_ATOM = {int_to_bytes(k): v for k, v in enumerate(KEYWORDS)} KEYWORD_TO_ATOM = {v: k for k, v in KEYWORD_FROM_ATOM.items()} -OP_REWRITE = { - "+": "add", - "-": "subtract", - "*": "multiply", - "/": "div", - "i": "if", - "c": "cons", - "f": "first", - "r": "rest", - "l": "listp", - "x": "raise", - "=": "eq", - ">": "gr", - ">s": "gr_bytes", -} - - -def op_table_for_module(mod): - return {k: v for k, v in mod.__dict__.items() if k.startswith("op_")} - - -def chia_operator_table(): - if native_opcodes_dict: - return native_opcodes_dict() - table = {} - table.update(op_table_for_module(core_ops)) - table.update(op_table_for_module(more_ops)) - return table - - -def chia_dialect_op_lookup(keyword_to_atom, op_rewrite): - op_table = chia_operator_table() - op_lookup = {} - for op, bytecode in keyword_to_atom.items(): - if op in "qa.": - continue - op_name = "op_%s" % op_rewrite.get(op, op) - op_f = op_table[op_name] - op_lookup[bytecode] = op_f - return op_lookup - -def chia_dialect(strict: bool, to_python: ConversionFn) -> Dialect: +def chia_dialect(strict: bool, to_python: ConversionFn, backend=None) -> Dialect: quote_kw = KEYWORD_TO_ATOM["q"] apply_kw = KEYWORD_TO_ATOM["a"] - opcode_lookup = chia_dialect_op_lookup(KEYWORD_TO_ATOM, OP_REWRITE) - if NativeDialect: - unknown_op_callback = ( - NATIVE_OP_UNKNOWN_STRICT if strict else NATIVE_OP_UNKNOWN_NON_STRICT - ) - dialect = NativeDialect( - quote_kw, - apply_kw, - unknown_op_callback, - to_python=to_python, - ) - else: - unknown_op_callback = ( - handle_unknown_op_strict if strict else handle_unknown_op_softfork_ready - ) - dialect = Dialect( - quote_kw, - apply_kw, - unknown_op_callback, - to_python=to_python, - ) - dialect.update(opcode_lookup) + dialect = new_dialect(quote_kw, apply_kw, strict, to_python, backend=backend) + table = opcode_table_for_backend(KEYWORD_TO_ATOM, backend=backend) + dialect.update(table) return dialect diff --git a/clvm/dialect.py b/clvm/dialect.py index 084f6d7a..c181b2b7 100644 --- a/clvm/dialect.py +++ b/clvm/dialect.py @@ -1,40 +1,69 @@ -from dataclasses import dataclass +from typing import Callable, Optional, Tuple + +try: + import clvm_rs +except ImportError: + clvm_rs = None + +from . import core_ops, more_ops +from .chainable_multi_op_fn import ChainableMultiOpFn +from .handle_unknown_op import ( + handle_unknown_op_softfork_ready, + handle_unknown_op_strict, +) +from .run_program import _run_program +from .types import CLVMObjectType, ConversionFn, MultiOpFn, OperatorDict -from typing import Any, Callable, Dict, Optional, Tuple, Union -from .run_program import _run_program +OP_REWRITE = { + "+": "add", + "-": "subtract", + "*": "multiply", + "/": "div", + "i": "if", + "c": "cons", + "f": "first", + "r": "rest", + "l": "listp", + "x": "raise", + "=": "eq", + ">": "gr", + ">s": "gr_bytes", +} -CLVMAtom = Any -CLVMPair = Any -CLVMObjectType = Union["CLVMAtom", "CLVMPair"] +def op_table_for_module(mod): + return {k: v for k, v in mod.__dict__.items() if k.startswith("op_")} -MultiOpFn = Callable[[bytes, CLVMObjectType, int], Tuple[int, CLVMObjectType]] +def op_imp_table_for_backend(backend): + if backend is None and clvm_rs: + backend = "native" -ConversionFn = Callable[[CLVMObjectType], CLVMObjectType] + if backend == "native": + if clvm_rs is None: + raise RuntimeError("native backend not installed") + return clvm_rs.native_opcodes_dict() -OpFn = Callable[[CLVMObjectType, int], Tuple[int, CLVMObjectType]] + table = {} + table.update(op_table_for_module(core_ops)) + table.update(op_table_for_module(more_ops)) + return table -OperatorDict = Dict[bytes, Callable[[CLVMObjectType, int], Tuple[int, CLVMObjectType]]] +def op_atom_to_imp_table(op_imp_table, keyword_to_atom, op_rewrite=OP_REWRITE): + op_atom_to_imp_table = {} + for op, bytecode in keyword_to_atom.items(): + op_name = "op_%s" % op_rewrite.get(op, op) + op_f = op_imp_table.get(op_name) + if op_f: + op_atom_to_imp_table[bytecode] = op_f + return op_atom_to_imp_table -@dataclass -class ChainableMultiOpFn: - op_lookup: OperatorDict - unknown_op_handler: MultiOpFn - def __call__( - self, op: bytes, arguments: CLVMObjectType, max_cost: Optional[int] = None - ) -> Tuple[int, CLVMObjectType]: - f = self.op_lookup.get(op) - if f: - try: - return f(arguments) - except TypeError: - # some operators require `max_cost` - return f(arguments, max_cost) - return self.unknown_op_handler(op, arguments, max_cost) +def opcode_table_for_backend(keyword_to_atom, backend): + op_imp_table = op_imp_table_for_backend(backend) + return op_atom_to_imp_table(op_imp_table, keyword_to_atom) class Dialect: @@ -76,3 +105,42 @@ def run_program( pre_eval_f, ) return cost, self.to_python(r) + + +def native_new_dialect( + quote_kw: bytes, apply_kw: bytes, strict: bool, to_python: ConversionFn +) -> Dialect: + unknown_op_callback = ( + clvm_rs.NATIVE_OP_UNKNOWN_STRICT + if strict + else clvm_rs.NATIVE_OP_UNKNOWN_NON_STRICT + ) + dialect = clvm_rs.Dialect( + quote_kw, + apply_kw, + unknown_op_callback, + to_python=to_python, + ) + return dialect + + +def python_new_dialect( + quote_kw: bytes, apply_kw: bytes, strict: bool, to_python: ConversionFn +) -> Dialect: + unknown_op_callback = ( + handle_unknown_op_strict if strict else handle_unknown_op_softfork_ready + ) + dialect = Dialect( + quote_kw, + apply_kw, + unknown_op_callback, + to_python=to_python, + ) + return dialect + + +def new_dialect(quote_kw: bytes, apply_kw: bytes, strict: bool, to_python: ConversionFn, backend=None): + if backend is None: + backend = "python" if clvm_rs is None else "native" + backend_f = native_new_dialect if backend == "native" else python_new_dialect + return backend_f(quote_kw, apply_kw, strict, to_python) diff --git a/clvm/operators.py b/clvm/operators.py index 82cb2cd4..07d164e7 100644 --- a/clvm/operators.py +++ b/clvm/operators.py @@ -7,7 +7,8 @@ from .CLVMObject import CLVMObject from .op_utils import operators_for_module from .handle_unknown_op import handle_unknown_op_softfork_ready -from .chia_dialect import KEYWORDS, OP_REWRITE, KEYWORD_FROM_ATOM, KEYWORD_TO_ATOM # noqa +from .chia_dialect import KEYWORDS, KEYWORD_FROM_ATOM, KEYWORD_TO_ATOM # noqa +from .dialect import OP_REWRITE class OperatorDict(dict): diff --git a/clvm/types.py b/clvm/types.py new file mode 100644 index 00000000..c9f794ba --- /dev/null +++ b/clvm/types.py @@ -0,0 +1,15 @@ +from typing import Any, Callable, Dict, Tuple, Union + + +CLVMAtom = Any +CLVMPair = Any + +CLVMObjectType = Union["CLVMAtom", "CLVMPair"] + +MultiOpFn = Callable[[bytes, CLVMObjectType, int], Tuple[int, CLVMObjectType]] + +ConversionFn = Callable[[CLVMObjectType], CLVMObjectType] + +OpFn = Callable[[CLVMObjectType, int], Tuple[int, CLVMObjectType]] + +OperatorDict = Dict[bytes, Callable[[CLVMObjectType, int], Tuple[int, CLVMObjectType]]] diff --git a/tests/operators_test.py b/tests/operators_test.py index 093b4c6b..c0282412 100644 --- a/tests/operators_test.py +++ b/tests/operators_test.py @@ -1,14 +1,14 @@ import unittest -from clvm.chia_dialect import chia_dialect_op_lookup +from clvm.chainable_multi_op_fn import ChainableMultiOpFn +from clvm.costs import CONCAT_BASE_COST +from clvm.dialect import opcode_table_for_backend from clvm.handle_unknown_op import handle_unknown_op_softfork_ready -from clvm.dialect import ChainableMultiOpFn -from clvm.operators import KEYWORD_TO_ATOM, OP_REWRITE +from clvm.operators import KEYWORD_TO_ATOM from clvm.EvalError import EvalError from clvm import SExp -from clvm.costs import CONCAT_BASE_COST -OPERATOR_LOOKUP = chia_dialect_op_lookup(KEYWORD_TO_ATOM, OP_REWRITE) +OPERATOR_LOOKUP = opcode_table_for_backend(KEYWORD_TO_ATOM, backend=None) MAX_COST = int(1e18) @@ -26,7 +26,10 @@ def test_unknown_op(self): self.assertRaises( KeyError, lambda: OPERATOR_LOOKUP[b"\xff\xff1337"](SExp.to(1337), None) ) - od = ChainableMultiOpFn(chia_dialect_op_lookup(KEYWORD_TO_ATOM, OP_REWRITE), self.unknown_handler) + od = ChainableMultiOpFn( + opcode_table_for_backend(KEYWORD_TO_ATOM, backend=None), + self.unknown_handler, + ) cost, ret = od(b"\xff\xff1337", SExp.to(1337), None) self.assertTrue(self.handler_called) self.assertEqual(cost, 42) @@ -80,6 +83,8 @@ def test_unknown_ops_last_bits(self): for suffix in [b"\x3f", b"\x0f", b"\x00", b"\x2c"]: # the cost is unchanged by the last byte self.assertEqual( - handle_unknown_op_softfork_ready(b"\x3c" + suffix, SExp.null(), max_cost=None), + handle_unknown_op_softfork_ready( + b"\x3c" + suffix, SExp.null(), max_cost=None + ), (61, SExp.null()), ) From fe9a2438d9536af5b640970ea149f4810637290d Mon Sep 17 00:00:00 2001 From: arty Date: Wed, 30 Jun 2021 11:28:57 -0700 Subject: [PATCH 08/11] My take on dialects, which I think is now uniform in interface with the run_program from stage_0 --- clvm/__init__.py | 3 +- clvm/chia_dialect.py | 101 ++++++++++++++++++++++++--------- clvm/chia_dialect_constants.py | 28 +++++++++ clvm/dialect.py | 7 ++- clvm/operators.py | 7 +-- setup.py | 1 + 6 files changed, 115 insertions(+), 32 deletions(-) create mode 100644 clvm/chia_dialect_constants.py diff --git a/clvm/__init__.py b/clvm/__init__.py index b0c4bf4c..3b05ea60 100644 --- a/clvm/__init__.py +++ b/clvm/__init__.py @@ -1,5 +1,6 @@ from .SExp import SExp -from .dialect import Dialect # noqa +from .dialect import Dialect +from .chia_dialect import dialect_factories # noqa from .operators import ( # noqa QUOTE_ATOM, # deprecated KEYWORD_TO_ATOM, diff --git a/clvm/chia_dialect.py b/clvm/chia_dialect.py index a35e4b21..0e7496b7 100644 --- a/clvm/chia_dialect.py +++ b/clvm/chia_dialect.py @@ -1,37 +1,86 @@ +from .SExp import SExp from .casts import int_to_bytes -from .dialect import ConversionFn, Dialect, new_dialect, opcode_table_for_backend +from .types import CLVMObjectType, ConversionFn, MultiOpFn, OperatorDict +from .chainable_multi_op_fn import ChainableMultiOpFn +from .handle_unknown_op import ( + handle_unknown_op_softfork_ready, + handle_unknown_op_strict, +) +from .dialect import ConversionFn, Dialect, new_dialect, opcode_table_for_backend, python_new_dialect, native_new_dialect +from .chia_dialect_constants import KEYWORDS, KEYWORD_FROM_ATOM, KEYWORD_TO_ATOM # noqa +from .operators import OPERATOR_LOOKUP -KEYWORDS = ( - # core opcodes 0x01-x08 - ". q a i c f r l x " +def configure_chia_dialect(dialect: Dialect, backend=None) -> Dialect: + quote_kw = KEYWORD_TO_ATOM["q"] + apply_kw = KEYWORD_TO_ATOM["a"] + table = opcode_table_for_backend(KEYWORD_TO_ATOM, backend=backend) + dialect.update(table) + return dialect - # opcodes on atoms as strings 0x09-0x0f - "= >s sha256 substr strlen concat . " - # opcodes on atoms as ints 0x10-0x17 - "+ - * / divmod > ash lsh " +def chia_dialect(strict: bool, to_python: ConversionFn, backend=None) -> Dialect: + dialect = new_dialect(quote_kw, apply_kw, strict, to_python, backend=backend) + return configure_chia_dialect(dialect, backend) - # opcodes on atoms as vectors of bools 0x18-0x1c - "logand logior logxor lognot . " +class DebugDialect(Dialect): + def __init__( + self, + quote_kw: bytes, + apply_kw: bytes, + multi_op_fn: MultiOpFn, + to_python: ConversionFn, + ): + super().__init__(quote_kw, apply_kw, multi_op_fn, to_python) + self.tracer = lambda x,y: None - # opcodes for bls 1381 0x1d-0x1f - "point_add pubkey_for_exp . " + def do_sha256_with_trace(self,prev): + def _run(value,max_cost=None): + try: + cost, result = prev(value) + except TypeError: + cost, result = prev(value,max_cost) - # bool opcodes 0x20-0x23 - "not any all . " + self.tracer(value,result) + return cost, result - # misc 0x24 - "softfork " -).split() + return _run -KEYWORD_FROM_ATOM = {int_to_bytes(k): v for k, v in enumerate(KEYWORDS)} -KEYWORD_TO_ATOM = {v: k for k, v in KEYWORD_FROM_ATOM.items()} + def configure(self,**kwargs): + if 'sha256_tracer' in kwargs: + self.tracer = kwargs['sha256_tracer'] -def chia_dialect(strict: bool, to_python: ConversionFn, backend=None) -> Dialect: - quote_kw = KEYWORD_TO_ATOM["q"] - apply_kw = KEYWORD_TO_ATOM["a"] - dialect = new_dialect(quote_kw, apply_kw, strict, to_python, backend=backend) - table = opcode_table_for_backend(KEYWORD_TO_ATOM, backend=backend) - dialect.update(table) - return dialect +def chia_python_new_dialect( + quote_kw: bytes, apply_kw: bytes, strict: bool, to_python: ConversionFn, + backend="python" +) -> Dialect: + unknown_op_callback = ( + handle_unknown_op_strict if strict else handle_unknown_op_softfork_ready + ) + + # Setup as a chia style clvm provider giving the chia operators. + return configure_chia_dialect( + DebugDialect(quote_kw,apply_kw,OPERATOR_LOOKUP,to_python), + backend + ) + + +# Dialect that can allow acausal tracing of sha256 hashes. +def debug_new_dialect( + quote_kw: bytes, apply_kw: bytes, strict: bool, to_python: ConversionFn, + backend="python" +) -> Dialect: + d = chia_python_new_dialect(quote_kw, apply_kw, strict, to_python, backend) + + # Override operators we want to track. + std_op_table = opcode_table_for_backend(KEYWORD_TO_ATOM, backend="python") + table = { b'\x0b': d.do_sha256_with_trace(std_op_table[b'\x0b']) } + d.update(table) + + return d + +dialect_factories = { + 'python': chia_python_new_dialect, + 'native': native_new_dialect, + 'debug': debug_new_dialect, +} diff --git a/clvm/chia_dialect_constants.py b/clvm/chia_dialect_constants.py new file mode 100644 index 00000000..8075d604 --- /dev/null +++ b/clvm/chia_dialect_constants.py @@ -0,0 +1,28 @@ +from .casts import int_to_bytes + +KEYWORDS = ( + # core opcodes 0x01-x08 + ". q a i c f r l x " + + # opcodes on atoms as strings 0x09-0x0f + "= >s sha256 substr strlen concat . " + + # opcodes on atoms as ints 0x10-0x17 + "+ - * / divmod > ash lsh " + + # opcodes on atoms as vectors of bools 0x18-0x1c + "logand logior logxor lognot . " + + # opcodes for bls 1381 0x1d-0x1f + "point_add pubkey_for_exp . " + + # bool opcodes 0x20-0x23 + "not any all . " + + # misc 0x24 + "softfork " +).split() + +KEYWORD_FROM_ATOM = {int_to_bytes(k): v for k, v in enumerate(KEYWORDS)} +KEYWORD_TO_ATOM = {v: k for k, v in KEYWORD_FROM_ATOM.items()} + diff --git a/clvm/dialect.py b/clvm/dialect.py index c181b2b7..f69a1485 100644 --- a/clvm/dialect.py +++ b/clvm/dialect.py @@ -1,10 +1,11 @@ from typing import Callable, Optional, Tuple - +from .SExp import SExp try: import clvm_rs except ImportError: clvm_rs = None +import io from . import core_ops, more_ops from .chainable_multi_op_fn import ChainableMultiOpFn from .handle_unknown_op import ( @@ -13,6 +14,7 @@ ) from .run_program import _run_program from .types import CLVMObjectType, ConversionFn, MultiOpFn, OperatorDict +from clvm.serialize import sexp_from_stream, sexp_to_stream OP_REWRITE = { @@ -80,6 +82,9 @@ def __init__( self.multi_op_fn = ChainableMultiOpFn(self.opcode_lookup, multi_op_fn) self.to_python = to_python + def configure(self, **kwargs): + pass + def update(self, d: OperatorDict) -> None: self.opcode_lookup.update(d) diff --git a/clvm/operators.py b/clvm/operators.py index 07d164e7..e89a2c73 100644 --- a/clvm/operators.py +++ b/clvm/operators.py @@ -7,9 +7,8 @@ from .CLVMObject import CLVMObject from .op_utils import operators_for_module from .handle_unknown_op import handle_unknown_op_softfork_ready -from .chia_dialect import KEYWORDS, KEYWORD_FROM_ATOM, KEYWORD_TO_ATOM # noqa from .dialect import OP_REWRITE - +from .chia_dialect_constants import KEYWORDS, KEYWORD_FROM_ATOM, KEYWORD_TO_ATOM # noqa class OperatorDict(dict): """ @@ -33,11 +32,11 @@ def __new__(class_, d: Dict, *args, **kwargs): self.unknown_op_handler = handle_unknown_op_softfork_ready return self - def __call__(self, op: bytes, arguments: CLVMObject) -> Tuple[int, CLVMObject]: + def __call__(self, op: bytes, arguments: CLVMObject, max_cost=None) -> Tuple[int, CLVMObject]: f = self.get(op) if f is None: try: - return self.unknown_op_handler(op, arguments, max_cost=None) + return self.unknown_op_handler(op, arguments, max_cost) except TypeError: return self.unknown_op_handler(op, arguments) else: diff --git a/setup.py b/setup.py index a1a86be7..6be83646 100755 --- a/setup.py +++ b/setup.py @@ -7,6 +7,7 @@ dependencies = [ "blspy>=0.9", + "clvm_rs>=0.1.8" ] dev_dependencies = [ From 46c708f238cfadfcc29cd45a914163dc6d7dac76 Mon Sep 17 00:00:00 2001 From: arty Date: Thu, 22 Jul 2021 11:43:48 -0700 Subject: [PATCH 09/11] Allow 'native' dialect to work --- clvm/chia_dialect_constants.py | 15 +++++++ clvm/dialect.py | 71 +++++++++++++++++++++++++++++++++- 2 files changed, 85 insertions(+), 1 deletion(-) diff --git a/clvm/chia_dialect_constants.py b/clvm/chia_dialect_constants.py index 8075d604..6e9feea6 100644 --- a/clvm/chia_dialect_constants.py +++ b/clvm/chia_dialect_constants.py @@ -26,3 +26,18 @@ KEYWORD_FROM_ATOM = {int_to_bytes(k): v for k, v in enumerate(KEYWORDS)} KEYWORD_TO_ATOM = {v: k for k, v in KEYWORD_FROM_ATOM.items()} +KEYWORD_TO_LONG_KEYWORD = { + "i": "op_if", + "c": "op_cons", + "f": "op_first", + "r": "op_rest", + "l": "op_listp", + "x": "op_raise", + "=": "op_eq", + "+": "op_add", + "-": "op_subtract", + "*": "op_multiply", + "/": "op_divmod", + ">": "op_gr", + ">s": "op_gr_bytes", +} diff --git a/clvm/dialect.py b/clvm/dialect.py index f69a1485..cfe23100 100644 --- a/clvm/dialect.py +++ b/clvm/dialect.py @@ -15,6 +15,7 @@ from .run_program import _run_program from .types import CLVMObjectType, ConversionFn, MultiOpFn, OperatorDict from clvm.serialize import sexp_from_stream, sexp_to_stream +from .chia_dialect_constants import KEYWORD_FROM_ATOM, KEYWORD_TO_LONG_KEYWORD OP_REWRITE = { @@ -112,6 +113,72 @@ def run_program( return cost, self.to_python(r) +class NativeDialect: + def __init__( + self, + quote_kw: bytes, + apply_kw: bytes, + multi_op_fn: MultiOpFn, + to_python: ConversionFn, + ): + native_dict = clvm_rs.native_opcodes_dict() + def get_native_op_for_kw(op, k): + kw = KEYWORD_TO_LONG_KEYWORD[k] if k in KEYWORD_TO_LONG_KEYWORD else "op_%s" % k + return (op, native_dict[kw]) + + native_opcode_names_by_opcode = dict( + get_native_op_for_kw(op, k) + for op, k in KEYWORD_FROM_ATOM.items() + if k not in "qa." + ) + + self.quote_kw = quote_kw + self.apply_kw = apply_kw + self.to_python = to_python + self.callbacks = multi_op_fn + self.held = clvm_rs.Dialect( + quote_kw, + apply_kw, + multi_op_fn, + to_python + ) + + self.held.update(native_opcode_names_by_opcode) + + + def update(self,d): + return self.held.update(d) + + + def clear(self) -> None: + return self.held.clear() + + + def run_program( + self, + program: CLVMObjectType, + env: CLVMObjectType, + max_cost: int, + pre_eval_f: Optional[ + Callable[[CLVMObjectType, CLVMObjectType], Tuple[int, CLVMObjectType]] + ] = None, + ) -> Tuple[int, CLVMObjectType]: + prog = io.BytesIO() + e = io.BytesIO() + sexp_to_stream(program, prog) + sexp_to_stream(env, e) + + return self.held.deserialize_and_run_program( + prog.getvalue(), + e.getvalue(), + max_cost, + pre_eval_f + ) + + def configure(self,**kwargs): + pass + + def native_new_dialect( quote_kw: bytes, apply_kw: bytes, strict: bool, to_python: ConversionFn ) -> Dialect: @@ -120,7 +187,8 @@ def native_new_dialect( if strict else clvm_rs.NATIVE_OP_UNKNOWN_NON_STRICT ) - dialect = clvm_rs.Dialect( + + dialect = NativeDialect( quote_kw, apply_kw, unknown_op_callback, @@ -135,6 +203,7 @@ def python_new_dialect( unknown_op_callback = ( handle_unknown_op_strict if strict else handle_unknown_op_softfork_ready ) + dialect = Dialect( quote_kw, apply_kw, From 30dd2da9ed06c574982eb3c6eb1a004f6fa721bf Mon Sep 17 00:00:00 2001 From: arty Date: Fri, 13 Aug 2021 15:31:17 -0700 Subject: [PATCH 10/11] format with black --- clvm/CLVMObject.py | 5 +++- clvm/SExp.py | 1 + clvm/chainable_multi_op_fn.py | 1 + clvm/chia_dialect.py | 53 ++++++++++++++++++++++------------ clvm/chia_dialect_constants.py | 6 ---- clvm/dialect.py | 45 +++++++++++++++-------------- clvm/handle_unknown_op.py | 2 +- clvm/more_ops.py | 24 ++++++++++----- clvm/operators.py | 5 +++- clvm/run_program.py | 2 +- tests/run_program_test.py | 7 ++--- tests/serialize_test.py | 20 ++++++++----- tests/to_sexp_test.py | 12 ++++++-- 13 files changed, 110 insertions(+), 73 deletions(-) diff --git a/clvm/CLVMObject.py b/clvm/CLVMObject.py index 7586968c..9ac935f4 100644 --- a/clvm/CLVMObject.py +++ b/clvm/CLVMObject.py @@ -20,7 +20,10 @@ def __new__(class_, v): self = super(CLVMObject, class_).__new__(class_) if isinstance(v, tuple): if len(v) != 2: - raise ValueError("tuples must be of size 2, cannot create CLVMObject from: %s" % str(v)) + raise ValueError( + "tuples must be of size 2, cannot create CLVMObject from: %s" + % str(v) + ) self.pair = v self.atom = None else: diff --git a/clvm/SExp.py b/clvm/SExp.py index 398fe108..baa30090 100644 --- a/clvm/SExp.py +++ b/clvm/SExp.py @@ -131,6 +131,7 @@ class SExp: elements implementing the CLVM object protocol. Exactly one of "atom" and "pair" must be None. """ + true: "SExp" false: "SExp" __null__: "SExp" diff --git a/clvm/chainable_multi_op_fn.py b/clvm/chainable_multi_op_fn.py index be25c8f4..4a5d5f8b 100644 --- a/clvm/chainable_multi_op_fn.py +++ b/clvm/chainable_multi_op_fn.py @@ -10,6 +10,7 @@ class ChainableMultiOpFn: This structure handles clvm operators. Given an atom, it looks it up in a `dict`, then falls back to calling `unknown_op_handler`. """ + op_lookup: OperatorDict unknown_op_handler: MultiOpFn diff --git a/clvm/chia_dialect.py b/clvm/chia_dialect.py index 0e7496b7..ae0d7ad3 100644 --- a/clvm/chia_dialect.py +++ b/clvm/chia_dialect.py @@ -6,10 +6,18 @@ handle_unknown_op_softfork_ready, handle_unknown_op_strict, ) -from .dialect import ConversionFn, Dialect, new_dialect, opcode_table_for_backend, python_new_dialect, native_new_dialect +from .dialect import ( + ConversionFn, + Dialect, + new_dialect, + opcode_table_for_backend, + python_new_dialect, + native_new_dialect, +) from .chia_dialect_constants import KEYWORDS, KEYWORD_FROM_ATOM, KEYWORD_TO_ATOM # noqa from .operators import OPERATOR_LOOKUP + def configure_chia_dialect(dialect: Dialect, backend=None) -> Dialect: quote_kw = KEYWORD_TO_ATOM["q"] apply_kw = KEYWORD_TO_ATOM["a"] @@ -22,6 +30,7 @@ def chia_dialect(strict: bool, to_python: ConversionFn, backend=None) -> Dialect dialect = new_dialect(quote_kw, apply_kw, strict, to_python, backend=backend) return configure_chia_dialect(dialect, backend) + class DebugDialect(Dialect): def __init__( self, @@ -31,28 +40,31 @@ def __init__( to_python: ConversionFn, ): super().__init__(quote_kw, apply_kw, multi_op_fn, to_python) - self.tracer = lambda x,y: None + self.tracer = lambda x, y: None - def do_sha256_with_trace(self,prev): - def _run(value,max_cost=None): + def do_sha256_with_trace(self, prev): + def _run(value, max_cost=None): try: cost, result = prev(value) except TypeError: - cost, result = prev(value,max_cost) + cost, result = prev(value, max_cost) - self.tracer(value,result) + self.tracer(value, result) return cost, result return _run - def configure(self,**kwargs): - if 'sha256_tracer' in kwargs: - self.tracer = kwargs['sha256_tracer'] + def configure(self, **kwargs): + if "sha256_tracer" in kwargs: + self.tracer = kwargs["sha256_tracer"] def chia_python_new_dialect( - quote_kw: bytes, apply_kw: bytes, strict: bool, to_python: ConversionFn, - backend="python" + quote_kw: bytes, + apply_kw: bytes, + strict: bool, + to_python: ConversionFn, + backend="python", ) -> Dialect: unknown_op_callback = ( handle_unknown_op_strict if strict else handle_unknown_op_softfork_ready @@ -60,27 +72,30 @@ def chia_python_new_dialect( # Setup as a chia style clvm provider giving the chia operators. return configure_chia_dialect( - DebugDialect(quote_kw,apply_kw,OPERATOR_LOOKUP,to_python), - backend + DebugDialect(quote_kw, apply_kw, OPERATOR_LOOKUP, to_python), backend ) # Dialect that can allow acausal tracing of sha256 hashes. def debug_new_dialect( - quote_kw: bytes, apply_kw: bytes, strict: bool, to_python: ConversionFn, - backend="python" + quote_kw: bytes, + apply_kw: bytes, + strict: bool, + to_python: ConversionFn, + backend="python", ) -> Dialect: d = chia_python_new_dialect(quote_kw, apply_kw, strict, to_python, backend) # Override operators we want to track. std_op_table = opcode_table_for_backend(KEYWORD_TO_ATOM, backend="python") - table = { b'\x0b': d.do_sha256_with_trace(std_op_table[b'\x0b']) } + table = {b"\x0b": d.do_sha256_with_trace(std_op_table[b"\x0b"])} d.update(table) return d + dialect_factories = { - 'python': chia_python_new_dialect, - 'native': native_new_dialect, - 'debug': debug_new_dialect, + "python": chia_python_new_dialect, + "native": native_new_dialect, + "debug": debug_new_dialect, } diff --git a/clvm/chia_dialect_constants.py b/clvm/chia_dialect_constants.py index 6e9feea6..fa906b46 100644 --- a/clvm/chia_dialect_constants.py +++ b/clvm/chia_dialect_constants.py @@ -3,22 +3,16 @@ KEYWORDS = ( # core opcodes 0x01-x08 ". q a i c f r l x " - # opcodes on atoms as strings 0x09-0x0f "= >s sha256 substr strlen concat . " - # opcodes on atoms as ints 0x10-0x17 "+ - * / divmod > ash lsh " - # opcodes on atoms as vectors of bools 0x18-0x1c "logand logior logxor lognot . " - # opcodes for bls 1381 0x1d-0x1f "point_add pubkey_for_exp . " - # bool opcodes 0x20-0x23 "not any all . " - # misc 0x24 "softfork " ).split() diff --git a/clvm/dialect.py b/clvm/dialect.py index cfe23100..1392cbb8 100644 --- a/clvm/dialect.py +++ b/clvm/dialect.py @@ -1,5 +1,6 @@ from typing import Callable, Optional, Tuple from .SExp import SExp + try: import clvm_rs except ImportError: @@ -115,15 +116,20 @@ def run_program( class NativeDialect: def __init__( - self, - quote_kw: bytes, - apply_kw: bytes, - multi_op_fn: MultiOpFn, - to_python: ConversionFn, + self, + quote_kw: bytes, + apply_kw: bytes, + multi_op_fn: MultiOpFn, + to_python: ConversionFn, ): native_dict = clvm_rs.native_opcodes_dict() + def get_native_op_for_kw(op, k): - kw = KEYWORD_TO_LONG_KEYWORD[k] if k in KEYWORD_TO_LONG_KEYWORD else "op_%s" % k + kw = ( + KEYWORD_TO_LONG_KEYWORD[k] + if k in KEYWORD_TO_LONG_KEYWORD + else "op_%s" % k + ) return (op, native_dict[kw]) native_opcode_names_by_opcode = dict( @@ -136,24 +142,16 @@ def get_native_op_for_kw(op, k): self.apply_kw = apply_kw self.to_python = to_python self.callbacks = multi_op_fn - self.held = clvm_rs.Dialect( - quote_kw, - apply_kw, - multi_op_fn, - to_python - ) + self.held = clvm_rs.Dialect(quote_kw, apply_kw, multi_op_fn, to_python) self.held.update(native_opcode_names_by_opcode) - - def update(self,d): + def update(self, d): return self.held.update(d) - def clear(self) -> None: return self.held.clear() - def run_program( self, program: CLVMObjectType, @@ -169,13 +167,10 @@ def run_program( sexp_to_stream(env, e) return self.held.deserialize_and_run_program( - prog.getvalue(), - e.getvalue(), - max_cost, - pre_eval_f + prog.getvalue(), e.getvalue(), max_cost, pre_eval_f ) - def configure(self,**kwargs): + def configure(self, **kwargs): pass @@ -213,7 +208,13 @@ def python_new_dialect( return dialect -def new_dialect(quote_kw: bytes, apply_kw: bytes, strict: bool, to_python: ConversionFn, backend=None): +def new_dialect( + quote_kw: bytes, + apply_kw: bytes, + strict: bool, + to_python: ConversionFn, + backend=None, +): if backend is None: backend = "python" if clvm_rs is None else "native" backend_f = native_new_dialect if backend == "native" else python_new_dialect diff --git a/clvm/handle_unknown_op.py b/clvm/handle_unknown_op.py index d30211b1..e495a647 100644 --- a/clvm/handle_unknown_op.py +++ b/clvm/handle_unknown_op.py @@ -118,7 +118,7 @@ def handle_unknown_op_softfork_ready( cost += length * CONCAT_COST_PER_BYTE cost *= cost_multiplier - if cost >= 2**32: + if cost >= 2 ** 32: raise EvalError("invalid operator", args.to(op)) return (cost, args.to(b"")) diff --git a/clvm/more_ops.py b/clvm/more_ops.py index abea509e..80a7c011 100644 --- a/clvm/more_ops.py +++ b/clvm/more_ops.py @@ -81,7 +81,9 @@ def args_as_int32(op_name, args: SExp): if arg.pair: raise EvalError("%s requires int32 args" % op_name, arg) if len(arg.atom) > 4: - raise EvalError("%s requires int32 args (with no leading zeros)" % op_name, arg) + raise EvalError( + "%s requires int32 args (with no leading zeros)" % op_name, arg + ) yield arg.as_int() @@ -89,7 +91,9 @@ def args_as_int_list(op_name, args, count): int_list = list(args_as_ints(op_name, args)) if len(int_list) != count: plural = "s" if count != 1 else "" - raise EvalError("%s takes exactly %d argument%s" % (op_name, count, plural), args) + raise EvalError( + "%s takes exactly %d argument%s" % (op_name, count, plural), args + ) return int_list @@ -106,7 +110,9 @@ def args_as_bool_list(op_name, args, count): bool_list = list(args_as_bools(op_name, args)) if len(bool_list) != count: plural = "s" if count != 1 else "" - raise EvalError("%s takes exactly %d argument%s" % (op_name, count, plural), args) + raise EvalError( + "%s takes exactly %d argument%s" % (op_name, count, plural), args + ) return bool_list @@ -249,7 +255,7 @@ def op_substr(args: SExp): s0 = a0.as_atom() if arg_count == 2: - i1, = list(args_as_int32("substr", args.rest())) + (i1,) = list(args_as_int32("substr", args.rest())) i2 = len(s0) else: i1, i2 = list(args_as_int32("substr", args.rest())) @@ -277,7 +283,9 @@ def op_concat(args: SExp): def op_ash(args): (i0, l0), (i1, l1) = args_as_int_list("ash", args, 2) if l1 > 4: - raise EvalError("ash requires int32 args (with no leading zeros)", args.rest().first()) + raise EvalError( + "ash requires int32 args (with no leading zeros)", args.rest().first() + ) if abs(i1) > 65535: raise EvalError("shift too large", args.to(i1)) if i1 >= 0: @@ -292,7 +300,9 @@ def op_ash(args): def op_lsh(args): (i0, l0), (i1, l1) = args_as_int_list("lsh", args, 2) if l1 > 4: - raise EvalError("lsh requires int32 args (with no leading zeros)", args.rest().first()) + raise EvalError( + "lsh requires int32 args (with no leading zeros)", args.rest().first() + ) if abs(i1) > 65535: raise EvalError("shift too large", args.to(i1)) # we actually want i0 to be an *unsigned* int @@ -344,7 +354,7 @@ def binop(a, b): def op_lognot(args): - (i0, l0), = args_as_int_list("lognot", args, 1) + ((i0, l0),) = args_as_int_list("lognot", args, 1) cost = LOGNOT_BASE_COST + l0 * LOGNOT_COST_PER_BYTE return malloc_cost(cost, args.to(~i0)) diff --git a/clvm/operators.py b/clvm/operators.py index e89a2c73..c02aaed2 100644 --- a/clvm/operators.py +++ b/clvm/operators.py @@ -10,6 +10,7 @@ from .dialect import OP_REWRITE from .chia_dialect_constants import KEYWORDS, KEYWORD_FROM_ATOM, KEYWORD_TO_ATOM # noqa + class OperatorDict(dict): """ This is a nice hack that adds `__call__` to a dictionary, so @@ -32,7 +33,9 @@ def __new__(class_, d: Dict, *args, **kwargs): self.unknown_op_handler = handle_unknown_op_softfork_ready return self - def __call__(self, op: bytes, arguments: CLVMObject, max_cost=None) -> Tuple[int, CLVMObject]: + def __call__( + self, op: bytes, arguments: CLVMObject, max_cost=None + ) -> Tuple[int, CLVMObject]: f = self.get(op) if f is None: try: diff --git a/clvm/run_program.py b/clvm/run_program.py index 7b7fcb17..0910f2c4 100644 --- a/clvm/run_program.py +++ b/clvm/run_program.py @@ -9,7 +9,7 @@ QUOTE_COST, PATH_LOOKUP_BASE_COST, PATH_LOOKUP_COST_PER_LEG, - PATH_LOOKUP_COST_PER_ZERO_BYTE + PATH_LOOKUP_COST_PER_ZERO_BYTE, ) # the "Any" below should really be "OpStackType" but diff --git a/tests/run_program_test.py b/tests/run_program_test.py index d64462ca..e5e5f47f 100644 --- a/tests/run_program_test.py +++ b/tests/run_program_test.py @@ -4,7 +4,6 @@ class BitTest(unittest.TestCase): - def test_msb_mask(self): self.assertEqual(msb_mask(0x0), 0x0) self.assertEqual(msb_mask(0x01), 0x01) @@ -17,6 +16,6 @@ def test_msb_mask(self): self.assertEqual(msb_mask(0x80), 0x80) self.assertEqual(msb_mask(0x44), 0x40) - self.assertEqual(msb_mask(0x2a), 0x20) - self.assertEqual(msb_mask(0xff), 0x80) - self.assertEqual(msb_mask(0x0f), 0x08) + self.assertEqual(msb_mask(0x2A), 0x20) + self.assertEqual(msb_mask(0xFF), 0x80) + self.assertEqual(msb_mask(0x0F), 0x08) diff --git a/tests/serialize_test.py b/tests/serialize_test.py index 786f1c95..70687bcb 100644 --- a/tests/serialize_test.py +++ b/tests/serialize_test.py @@ -2,7 +2,11 @@ import unittest from clvm import to_sexp_f -from clvm.serialize import (sexp_from_stream, sexp_buffer_from_stream, atom_to_byte_iterator) +from clvm.serialize import ( + sexp_from_stream, + sexp_buffer_from_stream, + atom_to_byte_iterator, +) TEXT = b"the quick brown fox jumps over the lazy dogs" @@ -13,12 +17,12 @@ def __init__(self, b): self.buf = b def read(self, n): - ret = b'' + ret = b"" while n > 0 and len(self.buf) > 0: ret += self.buf[0:1] self.buf = self.buf[1:] n -= 1 - ret += b' ' * n + ret += b" " * n return ret @@ -79,7 +83,7 @@ def test_long_blobs(self): def test_blob_limit(self): with self.assertRaises(ValueError): for b in atom_to_byte_iterator(LargeAtom()): - print('%02x' % b) + print("%02x" % b) def test_very_long_blobs(self): for size in [0x40, 0x2000, 0x100000, 0x8000000]: @@ -100,7 +104,7 @@ def test_very_deep_tree(self): self.check_serde(s) def test_deserialize_empty(self): - bytes_in = b'' + bytes_in = b"" with self.assertRaises(ValueError): sexp_from_stream(io.BytesIO(bytes_in), to_sexp_f) @@ -110,7 +114,7 @@ def test_deserialize_empty(self): def test_deserialize_truncated_size(self): # fe means the total number of bytes in the length-prefix is 7 # one for each bit set. 5 bytes is too few - bytes_in = b'\xfe ' + bytes_in = b"\xfe " with self.assertRaises(ValueError): sexp_from_stream(io.BytesIO(bytes_in), to_sexp_f) @@ -120,7 +124,7 @@ def test_deserialize_truncated_size(self): def test_deserialize_truncated_blob(self): # this is a complete length prefix. The blob is supposed to be 63 bytes # the blob itself is truncated though, it's less than 63 bytes - bytes_in = b'\xbf ' + bytes_in = b"\xbf " with self.assertRaises(ValueError): sexp_from_stream(io.BytesIO(bytes_in), to_sexp_f) @@ -134,7 +138,7 @@ def test_deserialize_large_blob(self): # we don't support blobs this large, and we should fail immediately when # exceeding the max blob size, rather than trying to read this many # bytes from the stream - bytes_in = b'\xfe' + b'\xff' * 6 + bytes_in = b"\xfe" + b"\xff" * 6 with self.assertRaises(ValueError): sexp_from_stream(InfiniteStream(bytes_in), to_sexp_f) diff --git a/tests/to_sexp_test.py b/tests/to_sexp_test.py index 36a73e49..5de88960 100644 --- a/tests/to_sexp_test.py +++ b/tests/to_sexp_test.py @@ -91,11 +91,17 @@ def pair(self) -> Optional[Tuple[Any, Any]]: if self.depth == 0: return None new_depth: int = self.depth - 1 - return (GeneratedTree(new_depth, self.val), GeneratedTree(new_depth, self.val + 2**new_depth)) + return ( + GeneratedTree(new_depth, self.val), + GeneratedTree(new_depth, self.val + 2 ** new_depth), + ) tree = SExp.to(GeneratedTree(5, 0)) - assert print_leaves(tree) == "0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 " + \ - "16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 " + assert ( + print_leaves(tree) + == "0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 " + + "16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 " + ) tree = SExp.to(GeneratedTree(3, 0)) assert print_leaves(tree) == "0 1 2 3 4 5 6 7 " From d5bf1314320f7145b85c4378cb185cbbfeb13cee Mon Sep 17 00:00:00 2001 From: arty Date: Mon, 16 Aug 2021 07:46:51 -0700 Subject: [PATCH 11/11] use KEYWORD_TO_ATOM instead of a constant for sha256 --- clvm/chia_dialect.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/clvm/chia_dialect.py b/clvm/chia_dialect.py index ae0d7ad3..4ea42d8f 100644 --- a/clvm/chia_dialect.py +++ b/clvm/chia_dialect.py @@ -88,7 +88,8 @@ def debug_new_dialect( # Override operators we want to track. std_op_table = opcode_table_for_backend(KEYWORD_TO_ATOM, backend="python") - table = {b"\x0b": d.do_sha256_with_trace(std_op_table[b"\x0b"])} + sha256_op = KEYWORD_TO_ATOM["sha256"] + table = {sha256_op: d.do_sha256_with_trace(std_op_table[sha256_op])} d.update(table) return d