diff --git a/LICENSE-PYTHON-BITCOINTX b/LICENSE-PYTHON-BITCOINTX new file mode 100644 index 00000000..c23e0f9d --- /dev/null +++ b/LICENSE-PYTHON-BITCOINTX @@ -0,0 +1,177 @@ +python-bitcointx is free software: you can redistribute it and/or modify it +under the terms of the GNU Lesser General Public License as published by the +Free Software Foundation, either version 3 of the License, or (at your option) +any later version. + +python-bitcointx is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License +below for more details. + + + + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. \ No newline at end of file diff --git a/electrumx/lib/avm/avm.py b/electrumx/lib/avm/avm.py new file mode 100644 index 00000000..473e4418 --- /dev/null +++ b/electrumx/lib/avm/avm.py @@ -0,0 +1,242 @@ +from bitcointx.core.atomicalsconsensus import ( + ConsensusVerifyScriptAvmExecute, + AtomicalConsensusExecutionError +) + +from bitcointx.core.script import ( + CScript +) +from electrumx.lib.avm.util import ( + validate_protocol_definition, + sort_protocol_args_by_fn, + encode_args_push_datas_minimal, + RequestBlockchainContext, + RequestTxContext, + ReactorContext, + RequestBlockchainContext, + ScriptContext +) +from electrumx.lib.atomicals_blueprint_builder import AtomicalsTransferBlueprintBuilder +from cbor2 import loads + +# Updates the atomicals_spent_at_inputs structure for any consumed atomicals via either deploy or call +def remove_consumed_atomicals(ft_adds, nft_puts, atomicals_spent_at_inputs): + print(f'ft_adds={ft_adds}') + print(f'nft_puts={nft_puts}') + print(f'atomicals_spent_at_inputs={atomicals_spent_at_inputs}') + # Clean up and remove the atomical entry if it was consumed + for idx, atomical_entry_list in atomicals_spent_at_inputs.items(): + i = 0 + for atomical_entry in atomical_entry_list: + atomical_id = atomical_entry['atomical_id'] + # It was consumed therefore remove it from the input + if ft_adds.get(atomical_id) or nft_puts.get(atomical_id): + del atomical_entry[i] + break + i += 1 + return + +class CallCommandResult: + def __init__(self, success, reactor_context, error=None): + self.success = success + self.reactor_context = reactor_context + self.error = error + +class CallCommand: + def __init__(self, logger, blockchain_context: RequestBlockchainContext, request_tx_context: RequestTxContext, protocol_mint_data, atomicals_spent_at_inputs, reactor_state: ReactorContext, reactor_atomical_mint_info): + self.logger = logger + self.blockchain_context = blockchain_context + self.request_tx_context = request_tx_context + self.reactor_atomical_mint_info = reactor_atomical_mint_info + self.atomicals_spent_at_inputs = atomicals_spent_at_inputs + self.protocol_mint_data = protocol_mint_data + self.reactor_state = reactor_state + is_valid, unlock_script, lock_script = self.validate_params() + self.is_valid = is_valid + self.unlock_script = unlock_script + self.lock_script = lock_script + + def prepare_call_script(self, protocol_mint_data, call_payload): + if not protocol_mint_data: + return False, None, None + + protocol_code = protocol_mint_data.get('code') + if not protocol_code: + return False, None, None + + reactor_name = call_payload.get('n') + if not reactor_name: + return False, None, None + + # method = call_payload.get('m') + # if not isinstance(method, int) or method <= 0: + # return False, None, None + + # success, status = validate_protocol_definition(protocol_mint_data) + # if not success: + # return False, None, None + + # named_axdgs = call_payload.get('args') + # protocol_fns = protocol_mint_data.get('fn') + # found_all_params, sorted_args = sort_protocol_args_by_fn(named_args, protocol_fns[method]) + # if not found_all_params: + # return False, None, None + # sorted_args_encoded_script = encode_args_push_datas_minimal(sorted_args) + # sorted_args_encoded_script += bytes.fromhex('00') # attach an OP_FALSE at the top to indicate it will execute the deploy branch of the script + + return True, call_payload.get('u', b''), protocol_code + + def validate_params(self): + # todo: do some sanity check to ensure the protocol code matches for the reactor with reactor_atomical_mint_info + validated_success, unlock_script, lock_script = self.prepare_call_script(self.protocol_mint_data, self.request_tx_context.payload) + return validated_success, unlock_script, lock_script + + def execute(self): + if not self.is_valid: + raise ValueError(f'execute fail not valid call setup') + + # Null dummy cbor context is not actually used by consensus library, but we pass in dummy data + script_context = ScriptContext(CScript(self.unlock_script), CScript(self.lock_script)) + # Sanity check that the reactor context has the defaults, the only values allowed to be set are the nft_incoming and ft_incoming + assert self.reactor_state.state_hash != None and len(self.reactor_state.state_hash) == 32 + # Just check state and balances decode correctly + loads(self.reactor_state.state) + loads(self.reactor_state.nft_balances) + loads(self.reactor_state.ft_balances) + # Ensure the withdraws are empty because they will be set later + assert loads(self.reactor_state.nft_withdraws) == {} + assert loads(self.reactor_state.ft_withdraws) == {} + # just check correctly decode cbor for nft_incoming and ft_incoming + loads(self.reactor_state.nft_incoming) + loads(self.reactor_state.ft_incoming) + try: + updated_reactor_state = ConsensusVerifyScriptAvmExecute(script_context, self.blockchain_context, self.request_tx_context, self.reactor_state) + # Quite overkill to deserialize entire CBOR, but we want to be sure a valid CBOR was returned + loads(updated_reactor_state.state) + loads(updated_reactor_state.ft_balances) + loads(updated_reactor_state.nft_balances) + print(f'updated_reactor_state.state={loads(updated_reactor_state.state)}') + print(f'updated_reactor_state.ft_balances={loads(updated_reactor_state.ft_balances)}') + print(f'updated_reactor_state.nft_balances={loads(updated_reactor_state.nft_balances)}') + if updated_reactor_state: + remove_consumed_atomicals(updated_reactor_state.ft_adds, updated_reactor_state.nft_puts, self.atomicals_spent_at_inputs) + return CallCommandResult(True, updated_reactor_state) + except AtomicalConsensusExecutionError as ex: + print(f'AtomicalConsensusExecutionError ex={ex}') + return CallCommandResult(False, ex) + + raise ValueError(f'Critical call error') + +class DeployCommandResult: + def __init__(self, success, reactor_context, error=None): + self.success = success + self.reactor_context = reactor_context + self.error = error + +class DeployCommand: + def __init__(self, logger, blockchain_context: RequestBlockchainContext, request_tx_context: RequestTxContext, protocol_mint_data, atomicals_spent_at_inputs, reactor_state: ReactorContext): + self.logger = logger + self.atomicals_spent_at_inputs = atomicals_spent_at_inputs + self.blockchain_context = blockchain_context + self.request_tx_context = request_tx_context + self.protocol_mint_data = protocol_mint_data + is_valid, unlock_script, lock_script = self.validate_params() + self.is_valid = is_valid + self.unlock_script = unlock_script + self.lock_script = lock_script + self.reactor_state = reactor_state + + def prepare_deploy_script(self, protocol_mint_data, deploy_payload): + if not protocol_mint_data: + return False, None, None + protocol_code = protocol_mint_data.get('code') + if not protocol_code: + return False, None, None + # print(f'prepare_deploy_script:3') + # success, status = validate_protocol_definition(protocol_mint_data) + # if not success: + # return False, None, None + # print(f'prepare_deploy_script:4') + # deploy_named_args = deploy_payload.get('args') + # protocol_fns = protocol_mint_data.get('fn') + # found_all_params, deploy_sorted_args = sort_protocol_args_by_fn(deploy_named_args, protocol_fns[0]) + # if not found_all_params: + # return False, None, None + # print(f'prepare_deploy_script:5') + # deploy_sorted_args_encoded_script = encode_args_push_datas_minimal(deploy_sorted_args) + # print(f'prepare_deploy_script:6') + # deploy_sorted_args_encoded_script = bytes.fromhex('00') # attach an OP_FALSE at the top to indicate it will execute the deploy branch of the script + return True, deploy_payload.get('u', b''), protocol_code + + def validate_params(self): + validated_success, unlock_script, lock_script = self.prepare_deploy_script(self.protocol_mint_data, self.request_tx_context.payload) + return validated_success, unlock_script, lock_script + + def execute(self): + if not self.is_valid: + raise ValueError(f'execute fail not valid deploy setup') + + # Null dummy cbor context is not actually used by consensus library, but we pass in dummy data + script_context = ScriptContext(CScript(self.unlock_script), CScript(self.lock_script)) + + # Sanity check that the reactor context has the defaults, the only values allowed to be set are the nft_incoming and ft_incoming + assert self.reactor_state.state_hash == bytes.fromhex('0000000000000000000000000000000000000000000000000000000000000000') + # Ensure the datas are empty because they will be set later + assert loads(self.reactor_state.state) == {} + assert loads(self.reactor_state.nft_balances) == {} + assert loads(self.reactor_state.ft_balances) == {} + assert loads(self.reactor_state.nft_withdraws) == {} + assert loads(self.reactor_state.ft_withdraws) == {} + # just check correctly decode cbor for nft_incoming and ft_incoming + loads(self.reactor_state.nft_incoming) + loads(self.reactor_state.ft_incoming) + + try: + updated_reactor_state = ConsensusVerifyScriptAvmExecute(script_context, self.blockchain_context, self.request_tx_context, self.reactor_state) + # Quite overkill to deserialize entire CBOR, but we want to be sure a valid CBOR was returned + loads(updated_reactor_state.state) + loads(updated_reactor_state.ft_balances) + loads(updated_reactor_state.nft_balances) + if updated_reactor_state: + remove_consumed_atomicals(updated_reactor_state.ft_adds, updated_reactor_state.nft_puts, self.atomicals_spent_at_inputs) + return DeployCommandResult(True, updated_reactor_state) + except AtomicalConsensusExecutionError as ex: + print(f'AtomicalConsensusExecutionError ex={ex}') + return DeployCommandResult(False, None, ex) + raise ValueError(f'Critical call error') + +class AVMFactory: + def __init__(self, logger, get_atomicals_id_mint_info, blockchain_context: RequestBlockchainContext, protocol_mint_data): + self.logger = logger + self.get_atomicals_id_mint_info = get_atomicals_id_mint_info + self.blockchain_context = blockchain_context + self.protocol_mint_data = protocol_mint_data + + def create_call_command(self, request_tx_context: RequestTxContext, atomicals_spent_at_inputs, reactor_state, reactor_atomical_mint_info): + return CallCommand(self.logger, self.blockchain_context, request_tx_context, self.protocol_mint_data, atomicals_spent_at_inputs, reactor_state, reactor_atomical_mint_info) + + def create_deploy_command(self, request_tx_context: RequestTxContext, atomicals_spent_at_inputs, reactor_state): + return DeployCommand(self.logger, self.blockchain_context, request_tx_context, self.protocol_mint_data, atomicals_spent_at_inputs, reactor_state) + + def create_token_incoming_structs(self, atomicals_spent_at_inputs): + # Make a summary of tokens input + nft_atomicals, ft_atomicals, _ = AtomicalsTransferBlueprintBuilder.build_atomical_input_summaries_by_type(self.get_atomicals_id_mint_info, atomicals_spent_at_inputs) + nft_atomicals_avm_struct = {} + ft_atomicals_avm_struct = {} + + for atomical_id, _ in nft_atomicals.items(): + # Todo: not sure if we need to reverse yet or not + # atomical_id_reversed = bytearray(atomical_id) + # atomical_id_reversed.reverse() + nft_atomicals_avm_struct[atomical_id.hex()] = { + "s": True + } + + for atomical_id, input_summary in ft_atomicals.items(): + # Todo: not sure if we need to reverse yet or not + # atomical_id_reversed = bytearray(atomical_id) + # atomical_id_reversed.reverse() + ft_atomicals_avm_struct[atomical_id.hex()] = { + "total": input_summary.total_atomical_value + } + return nft_atomicals_avm_struct, ft_atomicals_avm_struct diff --git a/electrumx/lib/avm/pow_funcs.py b/electrumx/lib/avm/pow_funcs.py new file mode 100644 index 00000000..30d7dbe6 --- /dev/null +++ b/electrumx/lib/avm/pow_funcs.py @@ -0,0 +1,27 @@ +import sha3 + +from electrumx.lib.hash import ( + sha512_256, + sha512_224, + sha512 +) + +from electrumx.lib.eaglesong import ( + EaglesongHash +) + +def calc_sha512(bytes_to_hash): + return sha512(bytes_to_hash) + +def calc_sha512_256(bytes_to_hash): + return sha512_256(bytes_to_hash) + +def calc_sha512_224(bytes_to_hash): + return sha512_224(bytes_to_hash) + +def calc_sha3_256(bytes_to_hash): + return sha3.sha3_256(bytes_to_hash).digest() + +def calc_eaglesong(bytes_to_hash): + return bytearray(EaglesongHash(bytes_to_hash)) + \ No newline at end of file diff --git a/electrumx/lib/avm/util.py b/electrumx/lib/avm/util.py new file mode 100644 index 00000000..49ffc69b --- /dev/null +++ b/electrumx/lib/avm/util.py @@ -0,0 +1,377 @@ + +import re +from electrumx.lib.util import ( + pack_le_uint64 +) +from electrumx.lib.util_atomicals import ( + serialize_tx_safe +) +import struct + +from electrumx.lib.hash import hash_to_hex_str + +from bitcointx.core.script import ( + CScriptOp, OP_1NEGATE +) + +from bitcointx.core._bignum import ( + bn2vch +) +import random +def sort_protocol_args_by_fn(named_args, protocol_fn): + params = protocol_fn.get('params') + if not params: + return True, [] + sorted_args = [] + for param in params: + param_name = param['name'] + param_type = param['type'] + if not(param_name in named_args): + return False, [] + value = named_args[param_name] + if param_type == 'int': + if not isinstance(value, int): + return False, [] + sorted_args.append({ + 'type': 'int', + 'value': value + }) + elif param_type == 'str': + if not isinstance(value, str): + return False, [] + sorted_args.append({ + 'type': 'str', + 'value': value + }) + elif param_type == 'bytes': + if not isinstance(value, bytes): + return False, [] + sorted_args.append({ + 'type': 'bytes', + 'value': value + }) + else: + # Only supports int, str and bytes for now + return False, [] + return True, sorted_args + +def encode_op_pushdata(d): + """Encode a PUSHDATA op, returning bytes""" + if len(d) < 0x4c: + return bytes([len(d)]) + d # OP_PUSHDATA + elif len(d) <= 0xff: + return b'\x4c' + bytes([len(d)]) + d # OP_PUSHDATA1 + elif len(d) <= 0xffff: + return b'\x4d' + struct.pack(b' 64 or len(field_name) <= 0: + return False + + return True + +def is_valid_avm_type_name(type_name): + if not type_name: + return False + + if not isinstance(type_name, str): + return False + + if len(type_name) > 64 or len(type_name) <= 0: + return False + + return True + +def validate_protocol_param(param): + if not isinstance(param, dict): + return False + + if not is_valid_avm_field_name(param.get('name')): + return False + + if not is_valid_avm_type_name(param.get('type')): + return False + + return True + +def validate_protocol_params(params): + if params and not isinstance(params, list): + return False + + if params: + param_names = {} + for param in params: + if not validate_protocol_param(param): + return False + param_name = param.get('name') + found_name = param_names.get(param_name) + if found_name: + # Name already exists + return False + param_names[found_name] = param_name + + return True + +def validate_protocol_fn_ctor(fn): + if not fn or not isinstance(fn, dict): + return False + + name = fn.get('name') + if not is_valid_avm_field_name(name) or name != 'ctor': + return False + + params = fn.get('params') + if not validate_protocol_params(params): + return False + + return True + +def validate_protocol_p(p): + if not p: + return False + + m = re.compile(r'^[a-z][a-z0-9\_]{0,11}$') + if m.match(p): + return True + + return True + +def validate_protocol_fn(fn): + if not fn or not isinstance(fn, dict): + return False + + name = fn.get('name') + if not is_valid_avm_field_name(name) or name == 'ctor': + return False + + params = fn.get('params') + if not validate_protocol_params(params): + return False + + return True + +def validate_protocol_fns(fns): + if not fns or len(fns) == 0 or not isinstance(fns, list): + return False + + if not validate_protocol_fn_ctor(fns[0]): + return False + + function_names = {} + for fn in fns[1:]: + if not validate_protocol_fn(fn): + return False + fn_name = fn.get('name') + found_name = function_names.get(fn_name) + if found_name: + # Name already exists + return False + function_names[found_name] = fn_name + + return True + +def validate_protocol_definition(def_data): + if not def_data or len(def_data) == 0: + return False, { + 'messages': [ + 'Empty' + ] + } + + if not validate_protocol_code(def_data.get('code')): + return False, { + 'messages': [ + 'Invalid code' + ] + } + + protocol_fns = def_data.get('fn') + if not validate_protocol_fns(protocol_fns): + return False, { + 'messages': [ + 'Invalid fn' + ] + } + + protocol_name = def_data.get('p') + if not validate_protocol_p(protocol_name): + False, { + 'messages': [ + 'Invalid p' + ] + } + + return True, { + 'messages': [ + 'Valid' + ] + } + +class RequestTxContext: + def __init__(self, coin, tx_hash, tx, payload): + self.tx = tx + rawtx_bytes = serialize_tx_safe(coin, tx_hash, tx) + self.rawtx_bytes = rawtx_bytes + self.tx_hash = tx_hash + self.tx_hash_str = hash_to_hex_str(tx_hash) + self.payload = payload + self.auth_public_key = self.payload.get('auth', b'') + +class ScriptContext: + def __init__(self, unlock, lock): + self.unlock_script = unlock + self.lock_script = lock + +class ReactorContext: + def __init__(self, state_hash, state, state_updates, state_deletes, nft_incoming, ft_incoming, nft_balances, nft_balances_updates, ft_balances, ft_balances_updates, nft_withdraws, ft_withdraws, ft_adds, nft_puts): + if state_hash == None: + self.state_hash = bytes.fromhex('0000000000000000000000000000000000000000000000000000000000000000') + else: + self.state_hash = state_hash + + self.state = state + self.state_updates = state_updates + self.state_deletes = state_deletes + self.ft_incoming = ft_incoming + self.nft_incoming = nft_incoming + self.ft_balances = ft_balances + self.ft_balances_updates = ft_balances_updates + self.nft_balances = nft_balances + self.nft_balances_updates = nft_balances_updates + self.ft_withdraws = ft_withdraws + self.nft_withdraws = nft_withdraws + self.ft_adds = ft_adds + self.nft_puts = nft_puts + +class RequestBlockchainContext: + def __init__(self, headers, current_height): + self.headers = headers + self.current_height = current_height + +class RequestInterpretParams: + def __init__(self, + tx_hash, + blockchain_context: RequestBlockchainContext, + lock_script_code, + unlock_script_code, + rawtx_bytes): + self.tx_hash = tx_hash + self.request_id = hash_to_hex_str(tx_hash) + self.blockchain_context = blockchain_context + self.lock_script_code = lock_script_code + self.unlock_script_code = unlock_script_code + self.rawtx_bytes = rawtx_bytes + +class ResponseError: + def __init__(self, error_code): + self.error_code = error_code + +class RequestReactorContext: + def __init__(self, contract_state, ft_balances, nft_balances): + self.contract_state = contract_state + self.ft_balances = ft_balances + self.nft_balances = nft_balances + +class ResponseInterpretParams: + def __init__(self, request_id, state_hash, result_contract_state, result_ft_incoming, result_ft_balances, result_nft_incoming, result_nft_balances, result_ft_withdraws, result_nft_withdraws): + self.request_id = request_id + self.state_hash = state_hash + self.result_contract_state = result_contract_state + self.result_ft_incoming = result_ft_incoming + self.result_ft_balances = result_ft_balances + self.result_nft_incoming = result_nft_incoming + self.result_nft_balances = result_nft_balances + self.result_ft_withdraws = result_ft_withdraws + self.result_nft_withdraws = result_nft_withdraws + + self.validate_state_hash() + + def validate_state_hash(self): + # Todo: Validate the external state hash provided matches the expected state hash from the calculation here for sanity check + assert False + + + +def print_result_states(obj): + if isinstance(obj, ResponseInterpretParams): + print('------------------------------------------------') + print(f'result_contract_state={r.result_contract_state}') + print(f'result_ft_balances={r.result_ft_balances}') + print(f'result_nft_balances={r.result_nft_balances}') + print(f'result_ft_withdraw={r.result_ft_withdraw}') + print(f'result_nft_withdraw={r.result_nft_withdraw}') + print('------------------------------------------------') + if isinstance(obj, ResponseError): + print('------------------------------------------------') + print(f'error_code={r.error_code}') + print('------------------------------------------------') diff --git a/electrumx/lib/eaglesong.py b/electrumx/lib/eaglesong.py new file mode 100644 index 00000000..d9e83fdc --- /dev/null +++ b/electrumx/lib/eaglesong.py @@ -0,0 +1,182 @@ +def PrintState( state ): + s = "" + for i in range(0, 16): + s += "0x%08x" % state[i] + s += " " + print(s) + +def EaglesongPermutation( state ): + N = 43 + #PrintState(state) + for i in range(0, N): + state = EaglesongRound(state, i) + return state + +def EaglesongRound( state, index ): + # constants + bitmatrix = [[1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1], + [0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1], + [0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1], + [0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0], + [1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1], + [1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0], + [1, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1], + [0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 1], + [0, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 1], + [0, 0, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1], + [0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1], + [1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0], + [0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0], + [0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0], + [1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1]] + coefficients = [[0, 2, 4], [0, 13, 22], [0, 4, 19], [0, 3, 14], [0, 27, 31], [0, 3, 8], [0, 17, 26], [0, 3, 12], [0, 18, 22], [0, 12, 18], [0, 4, 7], [0, 4, 31], [0, 12, 27], [0, 7, 17], [0, 7, 8], [0, 1, 13]] + injection_constants = [ 0x6e9e40ae , 0x71927c02 , 0x9a13d3b1 , 0xdaec32ad , 0x3d8951cf , 0xe1c9fe9a , 0xb806b54c , 0xacbbf417 , + 0xd3622b3b , 0xa082762a , 0x9edcf1c0 , 0xa9bada77 , 0x7f91e46c , 0xcb0f6e4f , 0x265d9241 , 0xb7bdeab0 , + 0x6260c9e6 , 0xff50dd2a , 0x9036aa71 , 0xce161879 , 0xd1307cdf , 0x89e456df , 0xf83133e2 , 0x65f55c3d , + 0x94871b01 , 0xb5d204cd , 0x583a3264 , 0x5e165957 , 0x4cbda964 , 0x675fca47 , 0xf4a3033e , 0x2a417322 , + 0x3b61432f , 0x7f5532f2 , 0xb609973b , 0x1a795239 , 0x31b477c9 , 0xd2949d28 , 0x78969712 , 0x0eb87b6e , + 0x7e11d22d , 0xccee88bd , 0xeed07eb8 , 0xe5563a81 , 0xe7cb6bcf , 0x25de953e , 0x4d05653a , 0x0b831557 , + 0x94b9cd77 , 0x13f01579 , 0x794b4a4a , 0x67e7c7dc , 0xc456d8d4 , 0x59689c9b , 0x668456d7 , 0x22d2a2e1 , + 0x38b3a828 , 0x0315ac3c , 0x438d681e , 0xab7109c5 , 0x97ee19a8 , 0xde062b2e , 0x2c76c47b , 0x0084456f , + 0x908f0fd3 , 0xa646551f , 0x3e826725 , 0xd521788e , 0x9f01c2b0 , 0x93180cdc , 0x92ea1df8 , 0x431a9aae , + 0x7c2ea356 , 0xda33ad03 , 0x46926893 , 0x66bde7d7 , 0xb501cc75 , 0x1f6e8a41 , 0x685250f4 , 0x3bb1f318 , + 0xaf238c04 , 0x974ed2ec , 0x5b159e49 , 0xd526f8bf , 0x12085626 , 0x3e2432a9 , 0x6bd20c48 , 0x1f1d59da , + 0x18ab1068 , 0x80f83cf8 , 0x2c8c11c0 , 0x7d548035 , 0x0ff675c3 , 0xfed160bf , 0x74bbbb24 , 0xd98e006b , + 0xdeaa47eb , 0x05f2179e , 0x437b0b71 , 0xa7c95f8f , 0x00a99d3b , 0x3fc3c444 , 0x72686f8e , 0x00fd01a9 , + 0xdedc0787 , 0xc6af7626 , 0x7012fe76 , 0xf2a5f7ce , 0x9a7b2eda , 0x5e57fcf2 , 0x4da0d4ad , 0x5c63b155 , + 0x34117375 , 0xd4134c11 , 0x2ea77435 , 0x5278b6de , 0xab522c4c , 0xbc8fc702 , 0xc94a09e4 , 0xebb93a9e , + 0x91ecb65e , 0x4c52ecc6 , 0x8703bb52 , 0xcb2d60aa , 0x30a0538a , 0x1514f10b , 0x157f6329 , 0x3429dc3d , + 0x5db73eb2 , 0xa7a1a969 , 0x7286bd24 , 0x0df6881e , 0x3785ba5f , 0xcd04623a , 0x02758170 , 0xd827f556 , + 0x99d95191 , 0x84457eb1 , 0x58a7fb22 , 0xd2967c5f , 0x4f0c33f6 , 0x4a02099a , 0xe0904821 , 0x94124036 , + 0x496a031b , 0x780b69c4 , 0xcf1a4927 , 0x87a119b8 , 0xcdfaf4f8 , 0x4cf9cd0f , 0x27c96a84 , 0x6d11117e , + 0x7f8cf847 , 0x74ceede5 , 0xc88905e6 , 0x60215841 , 0x7172875a , 0x736e993a , 0x010aa53c , 0x43d53c2b , + 0xf0d91a93 , 0x0d983b56 , 0xf816663c , 0xe5d13363 , 0x0a61737c , 0x09d51150 , 0x83a5ac2f , 0x3e884905 , + 0x7b01aeb5 , 0x600a6ea7 , 0xb7678f7b , 0x72b38977 , 0x068018f2 , 0xce6ae45b , 0x29188aa8 , 0xe5a0b1e9 , + 0xc04c2b86 , 0x8bd14d75 , 0x648781f3 , 0xdbae1e0a , 0xddcdd8ae , 0xab4d81a3 , 0x446baaba , 0x1cc0c19d , + 0x17be4f90 , 0x82c0e65d , 0x676f9c95 , 0x5c708db2 , 0x6fd4c867 , 0xa5106ef0 , 0x19dde49d , 0x78182f95 , + 0xd089cd81 , 0xa32e98fe , 0xbe306c82 , 0x6cd83d8c , 0x037f1bde , 0x0b15722d , 0xeddc1e22 , 0x93c76559 , + 0x8a2f571b , 0x92cc81b4 , 0x021b7477 , 0x67523904 , 0xc95dbccc , 0xac17ee9d , 0x944e46bc , 0x0781867e , + 0xc854dd9d , 0x26e2c30c , 0x858c0416 , 0x6d397708 , 0xebe29c58 , 0xc80ced86 , 0xd496b4ab , 0xbe45e6f5 , + 0x10d24706 , 0xacf8187a , 0x96f523cb , 0x2227e143 , 0x78c36564 , 0x4643adc2 , 0x4729d97a , 0xcff93e0d , + 0x25484bbd , 0x91c6798e , 0x95f773f4 , 0x44204675 , 0x2eda57ba , 0x06d313ef , 0xeeaa4466 , 0x2dfa7530 , + 0xa8af0c9b , 0x39f1535e , 0x0cc2b7bd , 0x38a76c0e , 0x4f41071d , 0xcdaf2475 , 0x49a6eff8 , 0x01621748 , + 0x36ebacab , 0xbd6d9a29 , 0x44d1cd65 , 0x40815dfd , 0x55fa5a1a , 0x87cce9e9 , 0xae559b45 , 0xd76b4c26 , + 0x637d60ad , 0xde29f5f9 , 0x97491cbb , 0xfb350040 , 0xffe7f997 , 0x201c9dcd , 0xe61320e9 , 0xa90987a3 , + 0xe24afa83 , 0x61c1e6fc , 0xcc87ff62 , 0xf1c9d8fa , 0x4fd04546 , 0x90ecc76e , 0x46e456b9 , 0x305dceb8 , + 0xf627e68c , 0x2d286815 , 0xc705bbfd , 0x101b6df3 , 0x892dae62 , 0xd5b7fb44 , 0xea1d5c94 , 0x5332e3cb , + 0xf856f88a , 0xb341b0e9 , 0x28408d9d , 0x5421bc17 , 0xeb9af9bc , 0x602371c5 , 0x67985a91 , 0xd774907f , + 0x7c4d697d , 0x9370b0b8 , 0x6ff5cebb , 0x7d465744 , 0x674ceac0 , 0xea9102fc , 0x0de94784 , 0xc793de69 , + 0xfe599bb1 , 0xc6ad952f , 0x6d6ca9c3 , 0x928c3f91 , 0xf9022f05 , 0x24a164dc , 0xe5e98cd3 , 0x7649efdb , + 0x6df3bcdb , 0x5d1e9ff1 , 0x17f5d010 , 0xe2686ea1 , 0x6eac77fe , 0x7bb5c585 , 0x88d90cbb , 0x18689163 , + 0x67c9efa5 , 0xc0b76d9b , 0x960efbab , 0xbd872807 , 0x70f4c474 , 0x56c29d20 , 0xd1541d15 , 0x88137033 , + 0xe3f02b3e , 0xb6d9b28d , 0x53a077ba , 0xeedcd29e , 0xa50a6c1d , 0x12c2801e , 0x52ba335b , 0x35984614 , + 0xe2599aa8 , 0xaf94ed1d , 0xd90d4767 , 0x202c7d07 , 0x77bec4f4 , 0xfa71bc80 , 0xfc5c8b76 , 0x8d0fbbfc , + 0xda366dc6 , 0x8b32a0c7 , 0x1b36f7fc , 0x6642dcbc , 0x6fe7e724 , 0x8b5fa782 , 0xc4227404 , 0x3a7d1da7 , + 0x517ed658 , 0x8a18df6d , 0x3e5c9b23 , 0x1fbd51ef , 0x1470601d , 0x3400389c , 0x676b065d , 0x8864ad80 , + 0xea6f1a9c , 0x2db484e1 , 0x608785f0 , 0x8dd384af , 0x69d26699 , 0x409c4e16 , 0x77f9986a , 0x7f491266 , + 0x883ea6cf , 0xeaa06072 , 0xfa2e5db5 , 0x352594b4 , 0x9156bb89 , 0xa2fbbbfb , 0xac3989c7 , 0x6e2422b1 , + 0x581f3560 , 0x1009a9b5 , 0x7e5ad9cd , 0xa9fc0a6e , 0x43e5998e , 0x7f8778f9 , 0xf038f8e1 , 0x5415c2e8 , + 0x6499b731 , 0xb82389ae , 0x05d4d819 , 0x0f06440e , 0xf1735aa0 , 0x986430ee , 0x47ec952c , 0xbf149cc5 , + 0xb3cb2cb6 , 0x3f41e8c2 , 0x271ac51b , 0x48ac5ded , 0xf76a0469 , 0x717bba4d , 0x4f5c90d6 , 0x3b74f756 , + 0x1824110a , 0xa4fd43e3 , 0x1eb0507c , 0xa9375c08 , 0x157c59a7 , 0x0cad8f51 , 0xd66031a0 , 0xabb5343f , + 0xe533fa43 , 0x1996e2bb , 0xd7953a71 , 0xd2529b94 , 0x58f0fa07 , 0x4c9b1877 , 0x057e990d , 0x8bfe19c4 , + 0xa8e2c0c9 , 0x99fcaada , 0x69d2aaca , 0xdc1c4642 , 0xf4d22307 , 0x7fe27e8c , 0x1366aa07 , 0x1594e637 , + 0xce1066bf , 0xdb922552 , 0x9930b52a , 0xaeaa9a3e , 0x31ff7eb4 , 0x5e1f945a , 0x150ac49c , 0x0ccdac2d , + 0xd8a8a217 , 0xb82ea6e5 , 0xd6a74659 , 0x67b7e3e6 , 0x836eef4a , 0xb6f90074 , 0x7fa3ea4b , 0xcb038123 , + 0xbf069f55 , 0x1fa83fc4 , 0xd6ebdb23 , 0x16f0a137 , 0x19a7110d , 0x5ff3b55f , 0xfb633868 , 0xb466f845 , + 0xbce0c198 , 0x88404296 , 0xddbdd88b , 0x7fc52546 , 0x63a553f8 , 0xa728405a , 0x378a2bce , 0x6862e570 , + 0xefb77e7d , 0xc611625e , 0x32515c15 , 0x6984b765 , 0xe8405976 , 0x9ba386fd , 0xd4eed4d9 , 0xf8fe0309 , + 0x0ce54601 , 0xbaf879c2 , 0xd8524057 , 0x1d8c1d7a , 0x72c0a3a9 , 0x5a1ffbde , 0x82f33a45 , 0x5143f446 , + 0x29c7e182 , 0xe536c32f , 0x5a6f245b , 0x44272adb , 0xcb701d9c , 0xf76137ec , 0x0841f145 , 0xe7042ecc , + 0xf1277dd7 , 0x745cf92c , 0xa8fe65fe , 0xd3e2d7cf , 0x54c513ef , 0x6079bc2d , 0xb66336b0 , 0x101e383b , + 0xbcd75753 , 0x25be238a , 0x56a6f0be , 0xeeffcc17 , 0x5ea31f3d , 0x0ae772f5 , 0xf76de3de , 0x1bbecdad , + 0xc9107d43 , 0xf7e38dce , 0x618358cd , 0x5c833f04 , 0xf6975906 , 0xde4177e5 , 0x67d314dc , 0xb4760f3e , + 0x56ce5888 , 0x0e8345a8 , 0xbff6b1bf , 0x78dfb112 , 0xf1709c1e , 0x7bb8ed8b , 0x902402b9 , 0xdaa64ae0 , + 0x46b71d89 , 0x7eee035f , 0xbe376509 , 0x99648f3a , 0x0863ea1f , 0x49ad8887 , 0x79bdecc5 , 0x3c10b568 , + 0x5f2e4bae , 0x04ef20ab , 0x72f8ce7b , 0x521e1ebe , 0x14525535 , 0x2e8af95b , 0x9094ccfd , 0xbcf36713 , + 0xc73953ef , 0xd4b91474 , 0x6554ec2d , 0xe3885c96 , 0x03dc73b7 , 0x931688a9 , 0xcbbef182 , 0x2b77cfc9 , + 0x632a32bd , 0xd2115dcc , 0x1ae5533d , 0x32684e13 , 0x4cc5a004 , 0x13321bde , 0x62cbd38d , 0x78383a3b , + 0xd00686f1 , 0x9f601ee7 , 0x7eaf23de , 0x3110c492 , 0x9c351209 , 0x7eb89d52 , 0x6d566eac , 0xc2efd226 , + 0x32e9fac5 , 0x52227274 , 0x09f84725 , 0xb8d0b605 , 0x72291f02 , 0x71b5c34b , 0x3dbfcbb8 , 0x04a02263 , + 0x55ba597f , 0xd4e4037d , 0xc813e1be , 0xffddeefa , 0xc3c058f3 , 0x87010f2e , 0x1dfcf55f , 0xc694eeeb , + 0xa9c01a74 , 0x98c2fc6b , 0xe57e1428 , 0xdd265a71 , 0x836b956d , 0x7e46ab1a , 0x5835d541 , 0x50b32505 , + 0xe640913c , 0xbb486079 , 0xfe496263 , 0x113c5b69 , 0x93cd6620 , 0x5efe823b , 0x2d657b40 , 0xb46dfc6c , + 0x57710c69 , 0xfe9fadeb , 0xb5f8728a , 0xe3224170 , 0xca28b751 , 0xfdabae56 , 0x5ab12c3c , 0xa697c457 , + 0xd28fa2b7 , 0x056579f2 , 0x9fd9d810 , 0xe3557478 , 0xd88d89ab , 0xa72a9422 , 0x6d47abd0 , 0x405bcbd9 , + 0x6f83ebaf , 0x13caec76 , 0xfceb9ee2 , 0x2e922df7 , 0xce9856df , 0xc05e9322 , 0x2772c854 , 0xb67f2a32 , + 0x6d1af28d , 0x3a78cf77 , 0xdff411e4 , 0x61c74ca9 , 0xed8b842e , 0x72880845 , 0x6e857085 , 0xc6404932 , + 0xee37f6bc , 0x27116f48 , 0x5e9ec45a , 0x8ea2a51f , 0xa5573db7 , 0xa746d036 , 0x486b4768 , 0x5b438f3b , + 0x18c54a5c , 0x64fcf08e , 0xe993cdc1 , 0x35c1ead3 , 0x9de07de7 , 0x321b841c , 0x87423c5e , 0x071aa0f6 , + 0x962eb75b , 0xbb06bdd2 , 0xdcdb5363 , 0x389752f2 , 0x83d9cc88 , 0xd014adc6 , 0xc71121bb , 0x2372f938 , + 0xcaff2650 , 0x62be8951 , 0x56dccaff , 0xac4084c0 , 0x09712e95 , 0x1d3c288f , 0x1b085744 , 0xe1d3cfef , + 0x5c9a812e , 0x6611fd59 , 0x85e46044 , 0x1981d885 , 0x5a4c903f , 0x43f30d4b , 0x7d1d601b , 0xdd3c3391 , + 0x030ec65e , 0xc12878cd , 0x72e795fe , 0xd0c76abd , 0x1ec085db , 0x7cbb61fa , 0x93e8dd1e , 0x8582eb06 , + 0x73563144 , 0x049d4e7e , 0x5fd5aefe , 0x7b842a00 , 0x75ced665 , 0xbb32d458 , 0x4e83bba7 , 0x8f15151f , + 0x7795a125 , 0xf0842455 , 0x499af99d , 0x565cc7fa , 0xa3b1278d , 0x3f27ce74 , 0x96ca058e , 0x8a497443 , + 0xa6fb8cae , 0xc115aa21 , 0x17504923 , 0xe4932402 , 0xaea886c2 , 0x8eb79af5 , 0xebd5ea6b , 0xc7980d3b , + 0x71369315 , 0x796e6a66 , 0x3a7ec708 , 0xb05175c8 , 0xe02b74e7 , 0xeb377ad3 , 0x6c8c1f54 , 0xb980c374 , + 0x59aee281 , 0x449cb799 , 0xe01f5605 , 0xed0e085e , 0xc9a1a3b4 , 0xaac481b1 , 0xc935c39c , 0xb7d8ce7f ] + + # bit matrix + new = [0 for i in range(0,16)] + for j in range(0, 16): + for k in range(0, 16): + new[j] = new[j] ^ (state[k] * bitmatrix[k][j]) + new[j] = new[j] & 0xffffffff # truncate to 32 bits, if necessary + state = new + + # circulant multiplication + for i in range(0, 16): + acc = 0 + for j in range(0, 3): + acc = acc ^ (state[i] << coefficients[i][j]) ^ (state[i] >> (32-coefficients[i][j])) + state[i] = acc & 0xffffffff # truncate to 32 bits, if necessary + + # constants injection + for i in range(0, 16): + state[i] = state[i] ^ injection_constants[index*16 + i] + + # add / rotate / add + for i in range(0, 8): + state[2*i] = (state[2*i] + state[2*i+1]) & 0xffffffff # truncate to 32 bits, if necessary + state[2*i] = (state[2*i] >> 24) ^ ((state[2*i] << 8) & 0xffffffff) # shift bytes + state[2*i+1] = (state[2*i+1] >> 8) ^ ((state[2*i+1] << 24) & 0xffffffff) # shift bytes + state[2*i+1] = (state[2*i] + state[2*i+1]) & 0xffffffff # truncate to 32 bits, if necessary + + return state + +def EaglesongSponge( input_bytes, num_output_bytes, delimiter ): + # parameters + capacity = 256 # must be multiple of 32 + rate = 256 # must be multiple of 32 + + state = [0 for i in range(0, 16)] + + # absorbing + for i in range(0, ((len(input_bytes)+1)*8+rate-1) // rate): + for j in range(0, rate//32): + integer = 0 + for k in range(0, 4): + if i*rate//8 + j*4 + k < len(input_bytes): + integer = (integer << 8) ^ input_bytes[i*rate//8 + j*4 + k] + elif i*rate//8 + j*4 + k == len(input_bytes): + integer = (integer << 8) ^ delimiter + state[j] = state[j] ^ integer + + state = EaglesongPermutation(state) + + # squeezing + output_bytes = [0] * num_output_bytes + for i in range(0, num_output_bytes//(rate//8)): + for j in range(0, rate//32): + for k in range(0, 4): + output_bytes[i*rate//8 + j*4 + k] = (state[j] >> (8*k)) & 0xff + + state = EaglesongPermutation(state) + + return output_bytes + +def EaglesongHash( input_bytes ): + # just run the sponge (with delimiter 0x06 -- hashing mode) and truncate to 32 bytes == 256 bits + return EaglesongSponge(bytearray(input_bytes), 32, 0x06) diff --git a/electrumx/lib/hash.py b/electrumx/lib/hash.py index 83ceca50..7d7a6fc0 100644 --- a/electrumx/lib/hash.py +++ b/electrumx/lib/hash.py @@ -30,7 +30,7 @@ import hmac from electrumx.lib.util import bytes_to_int, hex_to_bytes, int_to_bytes - +from Crypto.Hash import SHA512 _sha256 = hashlib.sha256 _new_hash = hashlib.new _hmac_digest = hmac.digest @@ -46,7 +46,24 @@ def double_sha256(x): """SHA-256 of SHA-256, as used extensively in bitcoin.""" return sha256(sha256(x)) - +def sha512_256(x): + '''SHA-512/256''' + firsthash = SHA512.new(truncate="256") + firsthash.update(x) + return firsthash.digest() + +def sha512(x): + '''SHA-512''' + firsthash = SHA512.new() + firsthash.update(x) + return firsthash.digest() + +def sha512_224(x): + '''SHA-512/224''' + firsthash = SHA512.new(truncate="224") + firsthash.update(x) + return firsthash.digest() + def hash_to_hex_str(x): """Convert a big-endian binary hash to displayed hex string. diff --git a/electrumx/lib/segwit_addr.py b/electrumx/lib/segwit_addr.py index 1e4cde26..1ae8de04 100644 --- a/electrumx/lib/segwit_addr.py +++ b/electrumx/lib/segwit_addr.py @@ -23,25 +23,21 @@ from enum import Enum - class Encoding(Enum): """Enumeration type to list the various supported encodings.""" - BECH32 = 1 BECH32M = 2 - CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l" -BECH32M_CONST = 0x2BC830A3 - +BECH32M_CONST = 0x2bc830a3 def bech32_polymod(values): """Internal function that computes the Bech32 checksum.""" - generator = [0x3B6A57B2, 0x26508E6D, 0x1EA119FA, 0x3D4233DD, 0x2A1462B3] + generator = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3] chk = 1 for value in values: top = chk >> 25 - chk = (chk & 0x1FFFFFF) << 5 ^ value + chk = (chk & 0x1ffffff) << 5 ^ value for i in range(5): chk ^= generator[i] if ((top >> i) & 1) else 0 return chk @@ -61,7 +57,6 @@ def bech32_verify_checksum(hrp, data): return Encoding.BECH32M return None - def bech32_create_checksum(hrp, data, spec): """Compute the checksum values given HRP and data.""" values = bech32_hrp_expand(hrp) + data @@ -73,27 +68,26 @@ def bech32_create_checksum(hrp, data, spec): def bech32_encode(hrp, data, spec): """Compute a Bech32 string given HRP and data values.""" combined = data + bech32_create_checksum(hrp, data, spec) - return hrp + "1" + "".join([CHARSET[d] for d in combined]) - + return hrp + '1' + ''.join([CHARSET[d] for d in combined]) def bech32_decode(bech): """Validate a Bech32/Bech32m string, and determine HRP and data.""" - if (any(ord(x) < 33 or ord(x) > 126 for x in bech)) or (bech.lower() != bech and bech.upper() != bech): + if ((any(ord(x) < 33 or ord(x) > 126 for x in bech)) or + (bech.lower() != bech and bech.upper() != bech)): return (None, None, None) bech = bech.lower() - pos = bech.rfind("1") + pos = bech.rfind('1') if pos < 1 or pos + 7 > len(bech) or len(bech) > 90: return (None, None, None) - if not all(x in CHARSET for x in bech[pos + 1 :]): + if not all(x in CHARSET for x in bech[pos+1:]): return (None, None, None) hrp = bech[:pos] - data = [CHARSET.find(x) for x in bech[pos + 1 :]] + data = [CHARSET.find(x) for x in bech[pos+1:]] spec = bech32_verify_checksum(hrp, data) if spec is None: return (None, None, None) return (hrp, data[:-6], spec) - def convertbits(data, frombits, tobits, pad=True): """General power-of-2 base conversion.""" acc = 0 @@ -145,4 +139,4 @@ def encode(hrp, witver, witprog): def segwit_scriptpubkey(witver, witprog): """Construct a Segwit scriptPubKey for a given witness program.""" - return bytes([witver + 0x50 if witver else 0, len(witprog)] + witprog) + return bytes([witver + 0x50 if witver else 0, len(witprog)] + witprog) \ No newline at end of file diff --git a/electrumx/lib/util_atomicals.py b/electrumx/lib/util_atomicals.py index 7d98bed0..38a1b936 100644 --- a/electrumx/lib/util_atomicals.py +++ b/electrumx/lib/util_atomicals.py @@ -219,6 +219,15 @@ def is_compact_atomical_id(value): return True return False +# Safely serialize a tx and validate the expected tx_hash matches +def serialize_tx_safe(coin, tx_hash, tx): + raw_tx = tx.serialize() + _tx, _tx_hash = coin.DESERIALIZER(raw_tx, 0).read_tx_and_hash() + assert(_tx == tx) + assert tx_hash == _tx_hash + del _tx + del _tx_hash + return raw_tx # Convert the compact string form to the expanded 36 byte sequence def compact_to_location_id_bytes(value): @@ -714,6 +723,50 @@ def populate_args_meta_ctx_init(mint_info, op_found_payload): ) return None, None mint_info["$immutable"] = True + ############################################ + # + # Atomicals Virtual Machine (AVM) Mint Operations + # + ############################################ + elif op_found_struct['op'] == 'def' and op_found_struct['input_index'] == 0: + mint_info['type'] = 'PROTOCOL' + # With AVMFactory the control fields are in the top level payload not the mint_info + # The reason is basically to simplify and optimize definitions + protocol = payload.get('p') + if isinstance(protocol, str) and protocol == '': + logger.warning(f'AVMFactory protocol name is invalid detected empty str {hash_to_hex_str(tx_hash)}. Skipping....') + return None, None + logger.debug(f'NFT request_protocol protocol name p {hash_to_hex_str(tx_hash)}, {protocol} {mint_info}') + if not isinstance(protocol, str) or not is_valid_protocol_string_name(protocol): + logger.warning(f'NFT request_protocol name p is invalid {hash_to_hex_str(tx_hash)}, {protocol} {mint_info}. Skipping....') + return None, None + mint_info['$request_protocol'] = protocol + # TODO: Perform sanity checks on the payload here... + + elif op_found_struct['op'] == 'new' and op_found_struct['input_index'] == 0: + mint_info['type'] = 'CONTRACT' + # With AVMFactory the control fields are in the top level payload not the mint_info + # The reason is basically to simplify and optimize definitions + contract_name = payload.get('name') + if isinstance(contract_name, str) and contract_name == '': + logger.warning(f'AVMFactory contract_name is invalid detected empty str {hash_to_hex_str(tx_hash)}. Skipping....') + return None, None + logger.debug(f'CONTRACT name {hash_to_hex_str(tx_hash)}, {contract_name} {mint_info}') + # Contract name can be empty + if isinstance(contract_name, str) and not is_valid_contract_string_name(contract_name): + logger.warning(f'CONTRACT name is invalid {hash_to_hex_str(tx_hash)}, {contract_name} {mint_info}. Skipping....') + return None, None + # If contract name is set then assign request_contract + if contract_name: + mint_info['$request_contract'] = contract_name + protocol_name = payload.get('p') + if not isinstance(protocol_name, str) or not is_valid_protocol_string_name(protocol_name): + logger.warning(f'CONTRACT p is invalid {hash_to_hex_str(tx_hash)}, {protocol_name} {mint_info}. Skipping....') + return None, None + mint_info['$instance_of_protocol'] = protocol_name + + logger.debug(f'CONTRACT name {hash_to_hex_str(tx_hash)}, {protocol_name} {contract_name} {mint_info}') + # TODO: Perform sanity checks on the payload here... ############################################ # @@ -1059,6 +1112,27 @@ def is_valid_container_string_name(container_name): return True return False +# A valid protocol string must begin with a-z0-9 and have up to 9 characters after it +# Including a-z0-9 +def is_valid_protocol_string_name(protocol_name): + if not is_valid_namebase_string_name(protocol_name): + return False + # Protocol names must start with alpha and maximum length 16 + m = re.compile(r'^[a-z][a-z0-9_]{0,15}$') + if m.match(protocol_name): + return True + return False + +# A valid contract string must begin with a-z0-9 and have up to 9 characters after it +# Including a-z0-9 +def is_valid_contract_string_name(contract_name): + if not is_valid_namebase_string_name(contract_name): + return False + # Contract names must start with alpha and maximum length 16 + m = re.compile(r'^[a-z][a-z0-9]{0,15}$') + if m.match(contract_name): + return True + return False # Is valid container item name # Including a-z0-9 and hyphen's "-" @@ -1140,6 +1214,10 @@ def parse_operation_from_script(script, n): atom_op_decoded = "dmt" # dmt - Mint tokens of distributed mint type (dft) elif atom_op == "03646174": atom_op_decoded = "dat" # dat - Store data on a transaction (dat) + elif atom_op == "03646566": + atom_op_decoded = "def" # def - Define avm protocol + elif atom_op == "036e6577": + atom_op_decoded = "new" # new - Instantiate new avm reactor contract from a protocol if atom_op_decoded: return atom_op_decoded, parse_atomicals_data_definition_operation(script, n + three_letter_op_len) diff --git a/electrumx/server/block_processor.py b/electrumx/server/block_processor.py index b0e93432..6cc3daa2 100644 --- a/electrumx/server/block_processor.py +++ b/electrumx/server/block_processor.py @@ -35,6 +35,7 @@ unpack_le_uint64_from, ) from electrumx.lib.util_atomicals import ( + serialize_tx_safe, DMINT_PATH, MINT_REALM_CONTAINER_TICKER_COMMIT_REVEAL_DELAY_BLOCKS, MINT_SUBNAME_COMMIT_PAYMENT_DELAY_BLOCKS, @@ -65,6 +66,8 @@ is_valid_realm_string_name, is_valid_regex, is_valid_subrealm_string_name, + is_valid_protocol_string_name, + is_valid_contract_string_name, is_valid_ticker_string, is_within_acceptable_blocks_for_general_reveal, is_within_acceptable_blocks_for_name_reveal, @@ -76,6 +79,17 @@ validate_dmitem_mint_args_with_container_dmint, validate_rules_data, ) +from electrumx.lib.avm.avm import ( + AVMFactory +) +from electrumx.lib.avm.util import ( + validate_protocol_definition, + RequestBlockchainContext, + RequestTxContext, + ResponseInterpretParams, + ReactorContext +) + from electrumx.server.daemon import Daemon, DaemonError from electrumx.server.db import COMP_TXID_LEN, DB, FlushData from electrumx.server.history import TXNUM_LEN @@ -282,6 +296,9 @@ def __init__(self, env: "Env", db: DB, daemon: Daemon, notifications: "Notificat self.distmint_data_cache = {} # Caches the distributed mints created self.state_data_cache = {} # Caches the state updates self.op_data_cache = {} # Caches the tx op + self.protocol_data_cache = {} # Caches the protocols created + self.reactor_data_cache = {} # Caches the reactors created + self.reactor_states_cache = {} # Caches the reactor states self.db_deletes = [] # If the lock is successfully acquired, in-memory chain state @@ -502,6 +519,9 @@ def flush_data(self): self.distmint_data_cache, self.state_data_cache, self.op_data_cache, + self.protocol_data_cache, + self.reactor_data_cache, + self.reactor_states_cache ) async def flush(self, flush_utxos): @@ -575,6 +595,36 @@ def advance_blocks(self, blocks): def get_atomicals_block_txs(self, height): return self.db.get_atomicals_block_txs(height) + # Helper method to validate if the transaction correctly cleanly assigns all FT (ARC20) tokens + # This method simulates coloring FT's according to split and regular rules + # Note: This does not apply to mempool but only prevout utxos that are confirmed + def validate_ft_rules_raw_tx(self, raw_tx): + # Deserialize the transaction + tx, tx_hash = self.coin.DESERIALIZER(bytes.fromhex(raw_tx), 0).read_tx_and_hash() + # Determine if there are any other operations at the transfer + operations_found_at_inputs = parse_protocols_operations_from_witness_array(tx, tx_hash, True) + # Build the map of the atomicals potentiall spent at the tx + atomicals_spent_at_inputs = self.build_atomicals_spent_at_inputs_for_validation_only(tx) + # Build a structure of organizing into NFT and FTs + # Note: We do not validate anything with NFTs, just FTs + # Build the "blueprint" for how to assign all atomicals + blueprint_builder = AtomicalsTransferBlueprintBuilder(self.logger, atomicals_spent_at_inputs, operations_found_at_inputs, tx_hash, tx, self.get_atomicals_id_mint_info, True, self.is_custom_coloring_activated(self.height)) + ft_output_blueprint = blueprint_builder.get_ft_output_blueprint() + # Log that there were tokens burned due to not being cleanly assigned + if blueprint_builder.get_are_fts_burned(): + encoded_atomicals_spent_at_inputs = encode_atomical_ids_hex(atomicals_spent_at_inputs) + encoded_ft_output_blueprint = auto_encode_bytes_items(encode_atomical_ids_hex(ft_output_blueprint)) + outputs = encoded_ft_output_blueprint['outputs'] + fts_burned = encoded_ft_output_blueprint['fts_burned'] + raise AtomicalsValidationError( + f'Invalid FT token inputs/outputs:\n' + f'tx_hash={hash_to_hex_str(tx_hash)}\n' + f'operations_found_at_inputs={operations_found_at_inputs}\n' + f'atomicals_spent_at_inputs={encoded_atomicals_spent_at_inputs}\n' + f'ft_output_blueprint.outputs={outputs}\n' + f'ft_output_blueprint.fts_burned={fts_burned}' + ) + # Query general data including the cache def get_general_data_with_cache(self, key): cache = self.general_data_cache.get(key) @@ -584,6 +634,16 @@ def get_general_data_with_cache(self, key): self.general_data_cache[key] = cache return cache + def get_atomicals_id_mint_data(self, atomical_id): + atomical_mint_data_key = b'md' + atomical_id + cache = self.general_data_cache.get(atomical_mint_data_key) + if cache: + return loads(cache) + db_mint_value = self.db.utxo_db.get(atomical_mint_data_key) + if not db_mint_value: + return None + return loads(db_mint_value) + # Get the mint information and LRU cache it for fast retrieval # Used for quickly getting the mint information for an atomical def get_atomicals_id_mint_info(self, atomical_id, with_cache): @@ -1344,40 +1404,107 @@ def create_or_delete_realm_entry_if_requested(self, mint_info, height, Delete): self.realm_data_cache, ) return True - + def create_or_delete_container_entry_if_requested(self, mint_info, height, Delete=False): request_container = mint_info.get("$request_container") if not request_container: # No name was requested, consider the operation successful noop - return True + return True if not is_valid_container_string_name(request_container): - return False - + return False + # Also check that there is no candidates already committed earlier than the current one status, atomical_id, candidates = self.get_effective_container(request_container, height) for candidate in candidates: if candidate["tx_num"] < mint_info["commit_tx_num"]: return False - if Delete: - self.delete_name_element_template( - b"co", - b"", - request_container, - mint_info["commit_tx_num"], - mint_info["id"], - self.container_data_cache, - ) - else: - self.put_name_element_template( - b"co", - b"", - request_container, - mint_info["commit_tx_num"], - mint_info["id"], - self.container_data_cache, - ) - return True + if Delete: + self.delete_name_element_template(b'co', b'', request_container, mint_info['commit_tx_num'], mint_info['id'], self.container_data_cache) + else: + self.put_name_element_template(b'co', b'', request_container, mint_info['commit_tx_num'], mint_info['id'], self.container_data_cache) + return True + + def create_or_delete_protocol_entry_if_requested(self, mint_info, height, Delete=False): + request_protocol = mint_info.get('$request_protocol') + if not request_protocol: + # No name was requested, consider the operation successful noop + return True + + if not is_valid_protocol_string_name(request_protocol): + return False + + self.logger.info(f'create_or_delete_protocol_entry_if_requested request_protocol={request_protocol}') + # Also check that there is no candidates already committed earlier than the current one + status, atomical_id, candidates = self.get_effective_protocol(request_protocol, height) + for candidate in candidates: + if candidate['tx_num'] < mint_info['commit_tx_num']: + return False + if Delete: + self.delete_name_element_template(b'pr', b'', request_protocol, mint_info['commit_tx_num'], mint_info['id'], self.protocol_data_cache) + else: + self.put_name_element_template(b'pr', b'', request_protocol, mint_info['commit_tx_num'], mint_info['id'], self.protocol_data_cache) + return True + + def create_or_delete_contract_entry_if_requested(self, protocol_atomical_id, mint_info, tx_hash, tx, header, protocol_mint_data, operations_found_at_inputs, atomicals_spent_at_inputs, height, Delete=False): + request_contract = mint_info.get('$request_contract') + if not request_contract: + # No name was requested, consider the operation successful noop + return True + + if not is_valid_contract_string_name(request_contract): + return False + + # Also check that there is no candidates already committed earlier than the current one + status, atomical_id, candidates = self.get_effective_contract(request_contract, height) + for candidate in candidates: + if candidate['tx_num'] < mint_info['commit_tx_num']: + return False + if Delete: + self.delete_name_element_template(b'cr', b'', request_contract, mint_info['commit_tx_num'], mint_info['id'], self.reactor_data_cache) + # We can use dummy reactor context for delete case + self.put_or_delete_reactor_states(mint_info['id'], None, height, True) + else: + # + # Add general blockchain context such as headers and height + # + headers = {} + # Todo add the last N=1000 headers potentially + headers[str(height)] = header.hex() + blockchain_context = RequestBlockchainContext(headers, height) + # Note atomicals_spent_at_inputs can be modified if the contract absorbs the tokens + avm_factory = AVMFactory(self.logger, self.get_atomicals_id_mint_info, blockchain_context, protocol_mint_data) + # + # + # Details about the transaction request itself + # + # + request_tx_context = RequestTxContext(self.coin, tx_hash, tx, operations_found_at_inputs.get('payload')) + # Note atomicals_spent_at_inputs can be modified if the contract absorbs the tokens + nft_incoming, ft_incoming = avm_factory.create_token_incoming_structs(atomicals_spent_at_inputs) + # Create the initial new reactor context which has no state_hash, no state and only optionally nft_incoming and ft_incomin + new_reactor_context = ReactorContext(None, dumps({}), dumps({}), dumps({}), dumps(nft_incoming), dumps(ft_incoming), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) + + # We have everything we need to validate an execute call + deploy_command = avm_factory.create_deploy_command(request_tx_context, atomicals_spent_at_inputs, new_reactor_context) + if not deploy_command.is_valid: + self.logger.info(f'create_or_delete_atomical: deploy of reactor for txid={hash_to_hex_str(tx_hash)} protocol_id={location_id_bytes_to_compact(protocol_atomical_id)} is_valid=False in Transaction {hash_to_hex_str(tx_hash)}. Skipping...') + return None + # Validated the execute call can proceed, now execute it + deploy_command_result = deploy_command.execute() + if not deploy_command_result.success: + self.logger.info(f'create_or_delete_atomical: deploy of reactor for txid={hash_to_hex_str(tx_hash)} protocol_id={location_id_bytes_to_compact(protocol_atomical_id)} failed in Transaction {hash_to_hex_str(tx_hash)}. Skipping...') + return False + + if not self.validate_and_create_nft_mint_utxo(mint_info, tx_hash): + self.logger.info(f'create_or_delete_atomical: validate_and_create_nft_mint_utxo (reactor) returned FALSE in Transaction {hash_to_hex_str(tx_hash)}. Skipping...') + return None + + new_reactor_atomical_id = mint_info['id'] + self.logger.info(f'atomical_id={new_reactor_atomical_id} deploy reactor_context: {deploy_command_result.reactor_context}') + self.put_or_delete_reactor_states(new_reactor_atomical_id, deploy_command_result.reactor_context, height, Delete) + self.put_name_element_template(b'cr', b'', request_contract, mint_info['commit_tx_num'], mint_info['id'], self.reactor_data_cache) + return True def create_or_delete_ticker_entry_if_requested(self, mint_info, height, Delete=False): request_ticker = mint_info.get("$request_ticker") @@ -1753,6 +1880,40 @@ def get_dmitem_parent_container_info(self, mint_info, mint_data_payload, height) self.logger.warning(f"get_dmitem_parent_container_info no_matched_price_point request_dmitem={request_dmitem}") return None, None + def get_latest_reactor_states(self, reactor_id): + found_reactor_record = self.reactor_states_cache.get(reactor_id) + found_cache_height = None + latest_state_cached = None + if found_reactor_record: + for state_key, state_item in sorted(found_reactor_record.items(), reverse=True): + found_cache_height = state_key + latest_state_cached = state_item + break + # As sanity check we also query database and make sure the cache is strictly >= for height than in db + # In the future we can remove this check to speed up processing inside of a block with many tx operating on same contract + latest_height_db, latest_state_db = self.db.get_latest_reactor_states(reactor_id) + if latest_height_db and found_cache_height: + assert latest_height_db <= found_cache_height + return found_cache_height, latest_state_cached + if found_cache_height: + return found_cache_height, latest_state_cached + if latest_height_db: + return latest_height_db, latest_state_db + return None, None + + def put_or_delete_reactor_states(self, reactor_id, reactor_context, height, Delete): + self.logger.info(f'put_or_delete_reactor_states reactor_id={location_id_bytes_to_compact(reactor_id)}') + found_reactor_record = self.reactor_states_cache.get(reactor_id) + if not Delete: + if not found_reactor_record: + self.reactor_states_cache[reactor_id] = {} + self.reactor_states_cache[reactor_id][height] = pickle.dumps(reactor_context) + else: + if found_reactor_record: + del self.reactor_states_cache[reactor_id][height] + db_key = b'rcs' + reactor_id + pack_be_uint32(height) + self.db_deletes.append(db_key) + # Check whether to create an atomical NFT/FT # Validates the format of the detected input operation and then checks the correct extra data is valid # such as realm, container, ticker, etc. Only succeeds if the appropriate names can be assigned @@ -1931,6 +2092,39 @@ def create_or_delete_atomical( else: self.put_op_data(tx_num, tx_hash, "mint-nft") + elif valid_create_op_type == 'PROTOCOL': + if not self.create_or_delete_protocol_entry_if_requested(mint_info, height, Delete): + return None + if not Delete: + self.logger.info(f'mint-protocol: {hash_to_hex_str(tx_hash)}') + self.put_op_data(tx_num, tx_hash, "mint-protocol") + + elif valid_create_op_type == 'CONTRACT': + # Ensure that protocol type exists before creating contract instance of it + instance_of_protocol = mint_info.get('$instance_of_protocol') + if instance_of_protocol: + status, protocol_atomical_id, not_used_candidates = self.get_effective_protocol(instance_of_protocol, height) + if status != 'verified' or not protocol_atomical_id: + self.logger.warning(f'create_or_delete_atomical: invalid_instance_of_protocol_not_found: instance_of_protocol={instance_of_protocol}, txid={hash_to_hex_str(tx_hash)}. Skipping...') + return None + mint_info['$instance_of_protocol_id'] = location_id_bytes_to_compact(protocol_atomical_id) + else: + self.logger.warning(f'create_or_delete_atomical: invalid_instance_of_protocol, txid={hash_to_hex_str(tx_hash)}. Skipping...') + return None + + if operations_found_at_inputs.get('input_index') != 0: + return None + + protocol_mint_data = self.get_atomicals_id_mint_data(protocol_atomical_id) + if not protocol_mint_data: + raise IndexError(f'create_or_delete_atomical:protocol_mint_data not found {location_id_bytes_to_compact(protocol_atomical_id)} {mint_info}') + + if not self.create_or_delete_contract_entry_if_requested(protocol_atomical_id, mint_info, tx_hash, tx, header, protocol_mint_data, operations_found_at_inputs, atomicals_spent_at_inputs, height, Delete): + return None + + if not Delete: + self.logger.info(f'mint-reactor: {hash_to_hex_str(tx_hash)}') + self.put_op_data(tx_num, tx_hash, "mint-reactor") elif valid_create_op_type == "FT": # Add $max_supply informative property if mint_info["subtype"] == "decentralized": @@ -2279,7 +2473,98 @@ def color_atomicals_outputs( ) return blueprint_builder + + def get_reactor_mint_info_and_data_by_id(self, payload, height): + if not payload: + return None, None + reactor_id = payload.get('id') + reactor_name = payload.get('n') + # Cannot set both reactor id and name, just one or the other + if reactor_id and reactor_name: + return None, None + if reactor_id: + reactor_mint_info = self.get_base_mint_info_by_atomical_id(reactor_id) # , self.get_atomicals_id_mint_data(reactor_id) + if reactor_mint_info.get('type') != 'CONTRACT': + raise IndexError(f'get_reactor_mint_info_and_data_by_id invalid contract type {location_id_bytes_to_compact(reactor_id)}') + return reactor_mint_info, self.get_atomicals_id_mint_data(reactor_mint_info['atomical_id']) + if reactor_name: + status, found_reactor_atomical_id, _ = self.get_effective_contract(reactor_name, height) + if status == 'verified' and found_reactor_atomical_id: + reactor_mint_info = self.get_base_mint_info_by_atomical_id(found_reactor_atomical_id) + if reactor_mint_info.get('type') != 'CONTRACT': + raise IndexError(f'get_reactor_mint_info_and_data_by_id invalid contract type look up by reactor_name {reactor_name} {location_id_bytes_to_compact(reactor_id)}') + assert found_reactor_atomical_id == reactor_mint_info['atomical_id'] + return reactor_mint_info, self.get_atomicals_id_mint_data(found_reactor_atomical_id) + return None, None + + # Create or delete the call data and transformations + def create_or_delete_call(self, operations_found_at_inputs, atomicals_spent_at_inputs, tx, tx_hash, tx_num, header, height, Delete): + if not operations_found_at_inputs or operations_found_at_inputs['op'] != 'c' or operations_found_at_inputs['input_index'] != 0: + return None + print(f'operations_found_at_inputs={operations_found_at_inputs}') + reactor_atomical_mint_info, mint_data = self.get_reactor_mint_info_and_data_by_id(operations_found_at_inputs['payload'], height) + if not reactor_atomical_mint_info: + return None + + reactor_id = reactor_atomical_mint_info['atomical_id'] + protocol_id = reactor_atomical_mint_info['mint_info']['$instance_of_protocol_id'] + protocol_mint_data = self.get_atomicals_id_mint_data(compact_to_location_id_bytes(protocol_id)) + if not protocol_mint_data: + raise IndexError(f'create_or_delete_call:protocol_mint_data not found {location_id_bytes_to_compact(protocol_id)}') + + # We have the reactor and protocol data + # Begin to construct the info for the call + # + # Reactor state and internal token table balances + # + latest_reactor_state_height, latest_reactor_state, = self.get_latest_reactor_states(reactor_id) + if not latest_reactor_state_height: + raise IndexError(f'Missing reactor state: {location_id_bytes_to_compact(reactor_id)}') + + # + # General blockchain context such as headers and height + # + headers = {} + # Todo add the last N=1000 headers potentially + headers[str(height)] = header.hex() + blockchain_context = RequestBlockchainContext(headers, height) + # Note atomicals_spent_at_inputs can be modified if the contract absorbs the tokens + avm_factory = AVMFactory(self.logger, self.get_atomicals_id_mint_info, blockchain_context, protocol_mint_data) + # + # + # Details about the transaction request itself + # + # + request_tx_context = RequestTxContext(self.coin, tx_hash, tx, operations_found_at_inputs.get('payload')) + nft_incoming, ft_incoming = avm_factory.create_token_incoming_structs(atomicals_spent_at_inputs) + + print(f'latest_reactor_state={latest_reactor_state}') + latest_reactor_state = pickle.loads(latest_reactor_state) + print(f'latest_reactor_state decoded={latest_reactor_state}') + + # Replace with the new incoming tokens + latest_reactor_state.nft_incoming = dumps(nft_incoming) + latest_reactor_state.ft_incoming = dumps(ft_incoming) + # Replace the withdraws with empty cbor + latest_reactor_state.nft_withdraws = dumps({}) + latest_reactor_state.ft_withdraws = dumps({}) + # We have everything we need to validate an execute call + call_command = avm_factory.create_call_command(request_tx_context, atomicals_spent_at_inputs, latest_reactor_state, reactor_atomical_mint_info) + if not call_command.is_valid: + self.logger.info(f'create_or_delete_call: call of reactor for txid={hash_to_hex_str(tx_hash)} protocol_id={protocol_id} is_valid=False in Transaction {hash_to_hex_str(tx_hash)}. Skipping...') + return None + # Validated the execute call can proceed, now execute it + call_command_result = call_command.execute() + if not call_command_result.success: + self.logger.info(f'create_or_delete_call: call of reactor for txid={hash_to_hex_str(tx_hash)} protocol_id={protocol_id} failed in Transaction {hash_to_hex_str(tx_hash)}. Skipping...') + return False + + self.put_or_delete_reactor_states(reactor_id, call_command_result.reactor_context, height, Delete) + # Absorbs all the atomicals tokens because the only way have gotten this far is if a payable method was called + atomicals_spent_at_inputs.clear() + return True + # Create or delete data that was found at the location def create_or_delete_data_location(self, tx_hash, operations_found_at_inputs, Delete=False): if not operations_found_at_inputs or operations_found_at_inputs["op"] != "dat": @@ -2390,7 +2675,15 @@ def get_effective_realm(self, realm_name, height): # Get the effective container considering cache and database def get_effective_container(self, container_name, height): return self.get_effective_name_template(b"co", container_name, height, self.container_data_cache) - + + # Get the effective protocol considering cache and database + def get_effective_protocol(self, protocol_name, height): + return self.get_effective_name_template(b'pr', protocol_name, height, self.protocol_data_cache) + + # Get the effective contract considering cache and database + def get_effective_contract(self, contract_name, height): + return self.get_effective_name_template(b'cr', contract_name, height, self.reactor_data_cache) + # Get the effective ticker considering cache and database def get_effective_ticker(self, ticker_name, height): return self.get_effective_name_template(b"tick", ticker_name, height, self.ticker_data_cache) @@ -2612,6 +2905,15 @@ def populate_extended_field_summary_atomical_info(self, atomical_id, atomical): db_mint_value = self.db.utxo_db.get(atomical_mint_data_key) if db_mint_value: decoded_object = loads(db_mint_value) + + # Populate specific fields based on type + if atomical['type'] == 'PROTOCOL': + protocol_success, status = validate_protocol_definition(decoded_object) + atomical['protocol_validation'] = { + 'success': protocol_success, + 'messages': status.get('messages') + } + unpacked_data_summary = auto_encode_bytes_elements(decoded_object) atomical["mint_data"] = {} if unpacked_data_summary is not None: @@ -2835,6 +3137,20 @@ def get_base_mint_info_by_atomical_id(self, atomical_id, height: Optional[int] = atomical["mint_info"]["$immutable"] = immutable else: atomical["mint_info"]["$immutable"] = False + + elif atomical['type'] == 'PROTOCOL': + # Attach any auxiliary information that was already successfully parsed before + request_protocol = init_mint_info.get('$request_protocol') + if request_protocol: + atomical['mint_info']['$request_protocol'] = request_protocol + + elif atomical['type'] == 'CONTRACT': + # Attach any auxiliary information that was already successfully parsed before + request_contract = init_mint_info.get('$request_contract') + if request_contract: + atomical['mint_info']['$request_contract'] = request_contract + atomical['mint_info']['$instance_of_protocol'] = init_mint_info.get('$instance_of_protocol') + atomical['mint_info']['$instance_of_protocol_id'] = init_mint_info.get('$instance_of_protocol_id') elif atomical["type"] == "FT": subtype = init_mint_info.get("subtype") @@ -3178,6 +3494,15 @@ def populate_dmitem_subtype_specific_fields(self, atomical, height: int): return request_dmitem, True return request_dmitem, False + # Populate the specific contract request type information + def populate_contract_subtype_specific_fields(self, atomical): + if atomical['type'] == 'CONTRACT': + atomical['$instance_of_protocol'] = atomical['mint_info'].get('$instance_of_protocol') + atomical['$instance_of_protocol_id'] = atomical['mint_info'].get('$instance_of_protocol_id') + request_contract = atomical['mint_info'].get('$request_contract') + if not request_contract: + return None, None + # Populate the subtype information such as realms, subrealms, containers and tickers # An atomical can have a naming element if it passed all the validity checks of the assignment # and for that reason there is the concept of "effective" name which is based on a commit/reveal delay pattern @@ -3213,7 +3538,36 @@ def populate_extended_atomical_subtype_info(self, atomical, height: int): # False indicates it is a request for the name, but it was not the current one atomical["subtype"] = "request_container" return atomical + # + # PROTOCOL Type Fields + # + the_name_request, is_atomical_name_verified_found = self.populate_name_subtype_specific_fields(atomical, 'protocol', self.get_effective_protocol, height) + if is_atomical_name_verified_found: + atomical['subtype'] = 'protocol' + atomical['$protocol'] = the_name_request + return atomical + elif the_name_request: + # False indicates it is a request for the name, but it was not the current one + atomical['subtype'] = 'request_protocol' + return atomical + + # + # CONTRACT Type Fields # + the_name_request, is_atomical_name_verified_found = self.populate_name_subtype_specific_fields(atomical, 'contract', self.get_effective_contract, height) + if is_atomical_name_verified_found: + atomical['subtype'] = 'contract' + atomical['$contract'] = the_name_request + # The method populates all the fields and nothing more needs to be done at this level for contracts + self.populate_contract_subtype_specific_fields(atomical) + + return atomical + elif the_name_request: + # False indicates it is a request for the name, but it was not the current one + atomical['subtype'] = 'request_contract' + return atomical + + # # TICKER NAME FIELDS # ( @@ -3657,7 +4011,7 @@ def advance_txs( tx, tx_hash, self.is_density_activated(height) ) if atomicals_operations_found_at_inputs: - # TODO + # TODO # Log information to help troubleshoot size_payload = sys.getsizeof(atomicals_operations_found_at_inputs["payload_bytes"]) operation_found = atomicals_operations_found_at_inputs["op"] @@ -3670,22 +4024,11 @@ def advance_txs( f"advance_txs: atomicals_operations_found_at_inputs operation_found={operation_found}, operation_input_index={operation_input_index}, size_payload={size_payload}, tx_hash={hash_to_hex_str(tx_hash)}, commit_txid={hash_to_hex_str(commit_txid)}, commit_index={commit_index}, reveal_location_txid={hash_to_hex_str(reveal_location_txid)}, reveal_location_index={reveal_location_index}" ) - # Color the outputs of any transferred NFT/FT atomicals according to the rules - blueprint_builder = self.color_atomicals_outputs( - atomicals_operations_found_at_inputs, - atomicals_spent_at_inputs, - tx, - tx_hash, - tx_num, - height, - ) - for atomical_id in blueprint_builder.get_atomical_ids_spent(): + # This call modifies the atomicals_spent_at_inputs if contract absorbs NFT/FT + request_id = self.create_or_delete_call(atomicals_operations_found_at_inputs, atomicals_spent_at_inputs, tx, tx_hash, tx_num, header, height, False) + if request_id: + already_found_valid_operation = True has_at_least_one_valid_atomicals_operation = True - self.logger.debug( - f"advance_txs: color_atomicals_outputs atomical_ids_transferred. atomical_id={atomical_id.hex()}, tx_hash={hash_to_hex_str(tx_hash)}" - ) - # Double hash the atomical_id to add it to the history to leverage the existing history db for all operations involving the atomical - append_hashX(double_sha256(atomical_id)) # Track whether we encountered a valid operation so we can skip other steps in the processing pipeline for efficiency already_found_valid_operation = False @@ -3711,7 +4054,7 @@ def advance_txs( ) if dft_count % 100 == 0: - self.logger.info(f"height={height}, dft_count={dft_count}") + self.logger.info(f'height={height}, dft_count={dft_count}') # Create NFT/FT atomicals if it is defined in the tx if not already_found_valid_operation: @@ -3725,16 +4068,25 @@ def advance_txs( tx, tx_hash, False, - ) + ) if created_atomical_id: already_found_valid_operation = True has_at_least_one_valid_atomicals_operation = True atomical_num += 1 # Double hash the created_atomical_id to add it to the history to leverage the existing history db for all operations involving the atomical append_hashX(double_sha256(created_atomical_id)) - self.logger.debug( - f"advance_txs: create_or_delete_atomical created_atomical_id atomical_id={created_atomical_id.hex()}, tx_hash={hash_to_hex_str(tx_hash)}" - ) + self.logger.debug(f'advance_txs: create_or_delete_atomical created_atomical_id atomical_id={created_atomical_id.hex()}, tx_hash={hash_to_hex_str(tx_hash)}') + + # Color the outputs of any transferred NFT/FT atomicals according to the rules + # Note: this was moved AFTER create_or_delete_atomical because we must account for clearing entries in atomicals_spent_at_inputs if + # the deploy call absorbed atomicals on reactor contract mint. + # The atomicals not absorbed by the call or deploy, just get transferred the normal way according to the predefined rules + blueprint_builder = self.color_atomicals_outputs(atomicals_operations_found_at_inputs, atomicals_spent_at_inputs, tx, tx_hash, tx_num, height) + for atomical_id in blueprint_builder.get_atomical_ids_spent(): + has_at_least_one_valid_atomicals_operation = True + self.logger.debug(f'advance_txs: color_atomicals_outputs atomical_ids_transferred. atomical_id={atomical_id.hex()}, tx_hash={hash_to_hex_str(tx_hash)}') + # Double hash the atomical_id to add it to the history to leverage the existing history db for all operations involving the atomical + append_hashX(double_sha256(atomical_id)) # Check if there were any regular 'dat' files definitions if not already_found_valid_operation: @@ -3825,18 +4177,9 @@ def advance_txs( concatenation_of_tx_hashes_with_valid_atomical_operation.append(tx_hash) if has_at_least_one_valid_atomicals_operation: - put_general_data( - b"th" + pack_le_uint32(height) + pack_le_uint64(tx_num) + tx_hash, - tx_hash, - ) - # only save the tx has at least one vaild atomical - raw_tx = tx.serialize() - _tx, _tx_hash = self.coin.DESERIALIZER(raw_tx, 0).read_tx_and_hash() - assert _tx == tx - assert _tx_hash == tx_hash - put_general_data(b"rtx" + tx_hash, raw_tx) - del _tx - del _tx_hash + put_general_data(b'th' + pack_le_uint32(height) + pack_le_uint64(tx_num) + tx_hash, tx_hash) + raw_tx = serialize_tx_safe(self.coin, tx_hash, tx) + put_general_data(b'rtx' + tx_hash, raw_tx) append_hashXs(hashXs) update_touched(hashXs) diff --git a/electrumx/server/db.py b/electrumx/server/db.py index 05615d5b..253caf8a 100644 --- a/electrumx/server/db.py +++ b/electrumx/server/db.py @@ -51,6 +51,8 @@ from electrumx.server.history import TXNUM_LEN, History from electrumx.server.storage import Storage, db_class +import pickle + if TYPE_CHECKING: from electrumx.server.env import Env @@ -117,7 +119,12 @@ class FlushData: state_adds = attr.ib() # type: Dict[bytes, Dict[bytes, bytes]] # op_adds is for record tx operation of one tx op_adds = attr.ib() # type: Dict[bytes, Dict[bytes]] - + # protocol_adds is for recording protocols + protocol_adds = attr.ib() # type: Dict[bytes, Dict[bytes] + # reactor_adds is for recording reactor contracts + reactor_adds = attr.ib() # type: Dict[bytes, Dict[bytes] + # reactor_states_adds is for recording reactor stats + reactor_states_adds = attr.ib() # type: Dict[bytes, Dict[bytes] COMP_TXID_LEN = 4 @@ -428,6 +435,9 @@ def assert_flushed(self, flush_data): assert not flush_data.dmpay_adds assert not flush_data.container_adds assert not flush_data.distmint_adds + assert not flush_data.protocol_adds + assert not flush_data.reactor_adds + assert not flush_data.reactor_states_adds assert not flush_data.state_adds assert not flush_data.deletes assert not flush_data.undo_infos @@ -611,6 +621,30 @@ def flush_utxo_db(self, batch, flush_data: FlushData): batch_put(key + pack_le_uint64(tx_num), pay_outpoint) flush_data.dmpay_adds.clear() + # protocol data adds + # Protocols are grouped by protocol name and distinguished by commit_tx_num + # The earliest commit_tx_num is the first-seen registration of the protocol name + batch_put = batch.put + for key, v in flush_data.protocol_adds.items(): + for tx_num, atomical_id in v.items(): + batch_put(key + pack_le_uint64(tx_num), atomical_id) + flush_data.protocol_adds.clear() + + # contract data adds + # Contracts are grouped by contract name and distinguished by commit_tx_num + # The earliest commit_tx_num is the first-seen registration of the contract name + batch_put = batch.put + for key, v in flush_data.reactor_adds.items(): + for tx_num, atomical_id in v.items(): + batch_put(key + pack_le_uint64(tx_num), atomical_id) + flush_data.reactor_adds.clear() + + # Add the reactor states + for reactor_id, reactor_height_state_map in flush_data.reactor_states_adds.items(): + for reactor_height, reactor_state in sorted(reactor_height_state_map.items(), reverse=True): + batch_put(b'rcs' + reactor_id + pack_be_uint32(reactor_height), pickle.dumps(reactor_state)) + flush_data.reactor_states_adds.clear() + # New UTXOs batch_put = batch.put for key, value in flush_data.adds.items(): @@ -1700,6 +1734,13 @@ def dump(self): lundofile.write(item + "\n") lundofile.close() + def get_latest_reactor_states(self, reactor_id): + db_key_prefix = b'rcs' + reactor_id + for db_key, db_value in self.utxo_db.iterator(prefix=db_key_prefix, reverse=True): + height, = unpack_be_uint32(db_key[-4:]) + return height, pickle.loads(db_value) + return None, None + def get_name_entries_template(self, db_prefix, subject_encoded): db_key_prefix = db_prefix + subject_encoded entries = [] diff --git a/electrumx/server/session/electrumx_session.py b/electrumx/server/session/electrumx_session.py index 4faae93a..83ccb9cf 100644 --- a/electrumx/server/session/electrumx_session.py +++ b/electrumx/server/session/electrumx_session.py @@ -183,6 +183,8 @@ def set_request_handlers(self, protocols): "blockchain.atomicals.get_by_container": self.ss.atomicals_get_by_container, "blockchain.atomicals.get_by_container_item": self.ss.atomicals_get_by_container_item, "blockchain.atomicals.get_by_container_item_validate": self.ss.atomicals_get_by_container_item_validation, + "blockchain.atomicals.get_by_protocol": self.ss.atomicals_get_by_protocol, + "blockchain.atomicals.get_by_contract": self.ss.atomicals_get_by_contract, "blockchain.atomicals.get_container_items": self.ss.atomicals_get_container_items, "blockchain.atomicals.find_tickers": self.ss.atomicals_search_tickers, "blockchain.atomicals.find_realms": self.ss.atomicals_search_realms, diff --git a/electrumx/server/session/http_session.py b/electrumx/server/session/http_session.py index c523d1c6..5fe6b9aa 100644 --- a/electrumx/server/session/http_session.py +++ b/electrumx/server/session/http_session.py @@ -132,6 +132,8 @@ async def add_endpoints(self, router, protocols): "blockchain.atomicals.get_by_container": self.ss.atomicals_get_by_container, "blockchain.atomicals.get_by_container_item": self.ss.atomicals_get_by_container_item, "blockchain.atomicals.get_by_container_item_validate": self.ss.atomicals_get_by_container_item_validation, + "blockchain.atomicals.get_by_protocol": self.ss.atomicals_get_by_protocol, + "blockchain.atomicals.get_by_contract": self.ss.atomicals_get_by_contract, "blockchain.atomicals.get_container_items": self.ss.atomicals_get_container_items, "blockchain.atomicals.find_tickers": self.ss.atomicals_search_tickers, "blockchain.atomicals.find_realms": self.ss.atomicals_search_realms, diff --git a/electrumx/server/session/session_base.py b/electrumx/server/session/session_base.py index 5991c805..c6624b49 100644 --- a/electrumx/server/session/session_base.py +++ b/electrumx/server/session/session_base.py @@ -141,3 +141,17 @@ async def handle_request(self, request): return await coro else: return coro + + +class LocalRPC(SessionBase): + """A local TCP RPC server session.""" + + processing_timeout = 10**9 # disable timeouts + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.client = "RPC" + self.connection.max_response_size = 0 + + def protocol_version_string(self): + return "RPC" diff --git a/electrumx/server/session/session_manager.py b/electrumx/server/session/session_manager.py index 4952fc26..78ea7cce 100644 --- a/electrumx/server/session/session_manager.py +++ b/electrumx/server/session/session_manager.py @@ -1,4 +1,5 @@ import asyncio +import copy import math import os import ssl diff --git a/electrumx/server/session/shared_session.py b/electrumx/server/session/shared_session.py index bb74b03f..2e6ff681 100644 --- a/electrumx/server/session/shared_session.py +++ b/electrumx/server/session/shared_session.py @@ -617,6 +617,56 @@ async def atomicals_get_by_container(self, container): } return {"result": return_result} + async def atomicals_get_by_protocol(self, name): + if not isinstance(name, str): + raise RPCError(BAD_REQUEST, f'empty protocol') + height = self.bp.height + status, candidate_atomical_id, all_entries = self.bp.get_effective_protocol(name, height) + formatted_entries = format_name_type_candidates_to_rpc(all_entries, self.bp.build_atomical_id_to_candidate_map(all_entries)) + + if candidate_atomical_id: + candidate_atomical_id = location_id_bytes_to_compact(candidate_atomical_id) + + found_atomical_id = None + if status == 'verified': + found_atomical_id = candidate_atomical_id + + return_result = { + 'status': status, + 'candidate_atomical_id': candidate_atomical_id, + 'atomical_id': found_atomical_id, + 'candidates': formatted_entries, + 'type': 'protocol' + } + return { + 'result': return_result + } + + async def atomicals_get_by_contract(self, name): + if not isinstance(name, str): + raise RPCError(BAD_REQUEST, f'empty contract') + height = self.session_mgr.bp.height + status, candidate_atomical_id, all_entries = self.session_mgr.bp.get_effective_contract(name, height) + formatted_entries = format_name_type_candidates_to_rpc(all_entries, self.session_mgr.bp.build_atomical_id_to_candidate_map(all_entries)) + + if candidate_atomical_id: + candidate_atomical_id = location_id_bytes_to_compact(candidate_atomical_id) + + found_atomical_id = None + if status == 'verified': + found_atomical_id = candidate_atomical_id + + return_result = { + 'status': status, + 'candidate_atomical_id': candidate_atomical_id, + 'atomical_id': found_atomical_id, + 'candidates': formatted_entries, + 'type': 'contract' + } + return { + 'result': return_result + } + async def atomicals_get_by_container_item(self, container, item_name): if not isinstance(container, str): raise RPCError(BAD_REQUEST, "empty container") diff --git a/electrumx_server b/electrumx_server index 0984d580..05b8f85e 100755 --- a/electrumx_server +++ b/electrumx_server @@ -57,4 +57,4 @@ def main(): if __name__ == "__main__": - main() + main() \ No newline at end of file diff --git a/requirements-test.txt b/requirements-test.txt index da318d56..618ebde2 100644 --- a/requirements-test.txt +++ b/requirements-test.txt @@ -20,5 +20,7 @@ git+https://github.com/VerusCoin/verushashpy bell-yespower cpupower bitweb_yespower==1.0.5 +pytest-asyncio +pysha3 python-bitcointx==1.1.5 -secp256k1==0.14.0 +secp256k1==0.14.0 \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 0d2cc418..13d6ff3c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,6 +10,10 @@ merkletools @ git+https://github.com/tierion/pymerkletools@f10d71e requests>=2.32.0,<2.33 python-dotenv>=1.0.0,<1.1 pre-commit==3.7.1 +python-bitcointx +pycryptodome +pysha3 # For LevelDB plyvel>=1.5.0,<1.6 + diff --git a/tests/lib/test_atomicalsconsensus_OP_CHECKAUTHSIG.py b/tests/lib/test_atomicalsconsensus_OP_CHECKAUTHSIG.py new file mode 100644 index 00000000..517ba3d7 --- /dev/null +++ b/tests/lib/test_atomicalsconsensus_OP_CHECKAUTHSIG.py @@ -0,0 +1,103 @@ + +import pytest + +from electrumx.lib.coins import Bitcoin +from electrumx.lib.hash import hex_str_to_hash + +from electrumx.lib.avm.avm import ( + AVMFactory, + RequestBlockchainContext, + RequestTxContext, + ReactorContext, + ScriptContext +) +from bitcointx.core.script import ( + CScript +) +from bitcointx.core.atomicalsconsensus import ( + ConsensusVerifyScriptAvmExecute, + AtomicalConsensusExecutionError +) +from cbor2 import dumps, loads +coin = Bitcoin + +class MockLogger: + def debug(self, msg): + return + def info(self, msg): + return + def warning(self, msg): + return + +def mock_mint_fetcher(atomical_id): + return { + } + +mock_current_header = '000000209174c9f2757e2647733c9ab69c133b90257f85ce7e9c0100000000000000000096e490f16d161416bef825bd4716b0914f344be5835cf6a8f0de89288a6c440391af0566d3620317aca8414a' +mock_headers = { + '840012': mock_current_header +} + +def test_execute_OP_CHECKAUTHSIG_with_valid_sig(): + rawtx_with_valid_sig = bytes.fromhex('01000000000101968502233a8ee5710490e98249727d618acf53dee6f078a4dc399363083c6ba70000000000ffffffff022202000000000000225120478a6ceb2d7bf2f88b0b644ba6c8b11b583722afca033dc8ea1c259cac50580200000000000000004c6a03736967463044022030b22811681b84763e3ecb9e5a4bc78344c48226c9f20e36192c46e39e294bb80220474d5b000eebe5ab48df53e12dfbc349b8fd4ac21d6e3839a873f0820f946a6c0340e0afc071d8c1ef4fbe56ee66b3a236d9195c95dfd778e1b8238d6e18498cd522460d444688d1339efe1bd24cbc97db42903f52e3c3b1cd0c8b3c620384a4c34c83209f76f4e90e426cae195e7092cf8ec81d3005a90574e6d0532c679983ad79d7eaac00630461746f6d01634c55a4616e63683431617541516461726773a36474696d651a66ae5292656e6f6e63650068626974776f726b63616164617574685821025388218c5ecdc718f49b7788306425f8577c88cacbaf48a2962a804bb64c6ec96821c19f76f4e90e426cae195e7092cf8ec81d3005a90574e6d0532c679983ad79d7ea00000000') + tx_with_valid_sig, tx_with_valid_sig_txid = coin.DESERIALIZER(rawtx_with_valid_sig, 0).read_tx_and_hash() + payload = { + 'auth': bytes.fromhex('025388218c5ecdc718f49b7788306425f8577c88cacbaf48a2962a804bb64c6ec9') + } + request_tx_context = RequestTxContext(coin, tx_with_valid_sig_txid, tx_with_valid_sig, payload) + state_hash = bytes.fromhex('0000000000000000000000000000000000000000000000000000000000000000') + reactor_context = ReactorContext(state_hash, dumps({ + '00': { + '0123': '4321' + } + }), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) + blockchain_context = RequestBlockchainContext(mock_headers, 840012) + script_context = ScriptContext(CScript(bytes.fromhex('51')), CScript(bytes.fromhex('51876351036d7367c10768656c6c6f2c207ef06851'))) + updated_reactor_state = ConsensusVerifyScriptAvmExecute(script_context, blockchain_context, request_tx_context, reactor_context) + print(f'{loads(updated_reactor_state.state)}') + assert loads(updated_reactor_state.state) == { + '00': { + '0123': '4321' + }, + '01': { + '6d7367': '025388218c5ecdc718f49b7788306425f8577c88cacbaf48a2962a804bb64c6ec968656c6c6f2c20' + } + } + state = loads(updated_reactor_state.state) + ft_incoming = loads(updated_reactor_state.ft_incoming) + nft_incoming = loads(updated_reactor_state.nft_incoming) + ft_balances = loads(updated_reactor_state.ft_balances) + nft_balances = loads(updated_reactor_state.nft_balances) + ft_withdraws = loads(updated_reactor_state.ft_withdraws) + nft_withdraws = loads(updated_reactor_state.nft_withdraws) + print(updated_reactor_state.state_hash.hex()) + assert updated_reactor_state.state_hash.hex() == 'e21b49dd4f89d022b59df656045a62112ca35dcb68fc22b0eeee111090523f5d' + assert len(state) == 2 + assert len(ft_incoming) == 0 + assert len(nft_incoming) == 0 + assert len(ft_balances) == 0 + assert len(nft_balances) == 0 + assert len(ft_withdraws) == 0 + assert len(nft_withdraws) == 0 + +def test_execute_OP_CHECKAUTHSIG_with_invalid_pubkey_sig(): + with pytest.raises(AtomicalConsensusExecutionError) as exc: + rawtx_with_valid_sig = bytes.fromhex('01000000000101968502233a8ee5710490e98249727d618acf53dee6f078a4dc399363083c6ba70000000000ffffffff022202000000000000225120478a6ceb2d7bf2f88b0b644ba6c8b11b583722afca033dc8ea1c259cac50580200000000000000004c6a03736967463044022030b22811681b84763e3ecb9e5a4bc78344c48226c9f20e36192c46e39e294bb80220474d5b000eebe5ab48df53e12dfbc349b8fd4ac21d6e3839a873f0820f946a6c0340e0afc071d8c1ef4fbe56ee66b3a236d9195c95dfd778e1b8238d6e18498cd522460d444688d1339efe1bd24cbc97db42903f52e3c3b1cd0c8b3c620384a4c34c83209f76f4e90e426cae195e7092cf8ec81d3005a90574e6d0532c679983ad79d7eaac00630461746f6d01634c55a4616e63683431617541516461726773a36474696d651a66ae5292656e6f6e63650068626974776f726b63616164617574685821025388218c5ecdc718f49b7788306425f8577c88cacbaf48a2962a804bb64c6ec96821c19f76f4e90e426cae195e7092cf8ec81d3005a90574e6d0532c679983ad79d7ea00000000') + tx_with_valid_sig, tx_with_valid_sig_txid = coin.DESERIALIZER(rawtx_with_valid_sig, 0).read_tx_and_hash() + payload = { + 'auth': bytes.fromhex('025388218c5ecdc718f49b7788306425f8577c88cacbaf48a2962a804bb64c6ec8') + } + request_tx_context = RequestTxContext(coin, tx_with_valid_sig_txid, tx_with_valid_sig, payload) + state_hash = bytes.fromhex('0000000000000000000000000000000000000000000000000000000000000000') + reactor_context = ReactorContext(state_hash, dumps({ + '00': { + '0123': '4321' + } + }), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) + blockchain_context = RequestBlockchainContext(mock_headers, 840012) + script_context = ScriptContext(CScript(bytes.fromhex('51')), CScript(bytes.fromhex('51876351036d7367c10768656c6c6f2c207ef06851'))) + updated_reactor_state = ConsensusVerifyScriptAvmExecute(script_context, blockchain_context, request_tx_context, reactor_context) + + assert exc.value.error_code == 0 + assert exc.value.script_error == 89 + assert exc.value.script_error_op_num == 5 \ No newline at end of file diff --git a/tests/lib/test_atomicalsconsensus_OP_DECODEBLOCKINFO.py b/tests/lib/test_atomicalsconsensus_OP_DECODEBLOCKINFO.py new file mode 100644 index 00000000..1df83e33 --- /dev/null +++ b/tests/lib/test_atomicalsconsensus_OP_DECODEBLOCKINFO.py @@ -0,0 +1,191 @@ +import pytest + +from cbor2 import dumps +from electrumx.lib.coins import Bitcoin +from electrumx.lib.hash import hex_str_to_hash +from electrumx.lib.avm.avm import ( + AVMFactory, + RequestBlockchainContext, + RequestTxContext, + ReactorContext +) +from electrumx.lib.avm.util import ( + encode_int_value, + encode_op_pushdata, + RequestBlockchainContext, +) +from bitcointx.core.atomicalsconsensus import ( + AtomicalConsensusExecutionError +) + +coin = Bitcoin +class MockLogger: + def debug(self, msg): + return + def info(self, msg): + return + def warning(self, msg): + return + +def mock_mint_fetcher(atomical_id): + return {} + +mock_current_header = '000000209174c9f2757e2647733c9ab69c133b90257f85ce7e9c0100000000000000000096e490f16d161416bef825bd4716b0914f344be5835cf6a8f0de89288a6c440391af0566d3620317aca8414a' +mock_headers = { + '840012': mock_current_header +} +mock_blockchain_context = RequestBlockchainContext(mock_headers, 840012) +mock_rawtx = bytes.fromhex('02000000018e469f953413e8d865fcf1f47d759772aa05e8d78b1e4577a58edc8bd09344ff010000006b483045022100ce16646785907c919a1658496a85cf3f3d877d98ffca5eabd189524acc1de53b02205ac24a9e2855db3da26b840c82fddaf73a5a6eff4b5b78cadfb00584ad6bd5f3012102cbcad7b21fb5fb08ad55eb09e327b97f63e8c5e99b2faf9bb330545a5bd4602cfeffffff0203761700000000001976a91496d02c013f734a642871261324c58091a806c23188ac9ce52300000000001976a914a9a8d5aa3ec73688d0540d45be3842f4603705c588ac6e640800') +mock_tx, mock_tx_hash = coin.DESERIALIZER(mock_rawtx, 0).read_tx_and_hash() +mock_empty_reactor_context = ReactorContext(None, dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) + +def test_atomicalsconsensus_OP_DECODEBLOCKINFO_fail_header_size(): + mock_current_header_wrong_len = bytes.fromhex(mock_current_header) + b'01' + mock_current_header_wrong_len_encoded = encode_op_pushdata(mock_current_header_wrong_len) + + protocol_mint_data = { + 'p': 'ppp', + 'code': bytes.fromhex(mock_current_header_wrong_len_encoded.hex() + '59fc5187'), + 'fn': [ + { + 'name': 'ctor', + 'params': [ + ] + } + ] + } + deploy_payload = { + 'p': 'ppp', + 'u': b'', + 'args': { + } + } + + avm = AVMFactory(MockLogger(), mock_mint_fetcher, mock_blockchain_context, protocol_mint_data) + atomicals_spent_at_inputs = {} + request_tx_context = RequestTxContext(coin, mock_tx_hash, mock_tx, deploy_payload) + deploy_command = avm.create_deploy_command(request_tx_context, atomicals_spent_at_inputs, mock_empty_reactor_context) + assert(deploy_command.is_valid) + assert(deploy_command.unlock_script.hex() == '') + assert(deploy_command.lock_script.hex() == '4c52000000209174c9f2757e2647733c9ab69c133b90257f85ce7e9c0100000000000000000096e490f16d161416bef825bd4716b0914f344be5835cf6a8f0de89288a6c440391af0566d3620317aca8414a303159fc5187') + result = deploy_command.execute() + assert not result.success + assert not result.reactor_context + assert result.error.error_code == 0 + assert result.error.script_error == 77 + assert result.error.script_error_op_num == 2 + +def test_atomicalsconsensus_OP_DECODEBLOCKINFO_invalid_item(): + mock_current_header_wrong_len = bytes.fromhex(mock_current_header) + mock_current_header_wrong_len_encoded = encode_op_pushdata(mock_current_header_wrong_len) + + protocol_mint_data = { + 'p': 'ppp', + 'code': bytes.fromhex(mock_current_header_wrong_len_encoded.hex() + '57fc5187'), + 'fn': [ + { + 'name': 'ctor', + 'params': [ + ] + } + ] + } + deploy_payload = { + 'p': 'ppp', + 'u': b'', + 'args': { + } + } + + avm = AVMFactory(MockLogger(), mock_mint_fetcher, mock_blockchain_context, protocol_mint_data) + atomicals_spent_at_inputs = {} + request_tx_context = RequestTxContext(coin, mock_tx_hash, mock_tx, deploy_payload) + deploy_command = avm.create_deploy_command(request_tx_context, atomicals_spent_at_inputs, mock_empty_reactor_context) + assert(deploy_command.is_valid) + assert(deploy_command.unlock_script.hex() == '') + assert(deploy_command.lock_script.hex() == '4c50000000209174c9f2757e2647733c9ab69c133b90257f85ce7e9c0100000000000000000096e490f16d161416bef825bd4716b0914f344be5835cf6a8f0de89288a6c440391af0566d3620317aca8414a57fc5187') + + result = deploy_command.execute() + assert not result.success + assert not result.reactor_context + assert result.error.error_code == 0 + assert result.error.script_error == 76 + assert result.error.script_error_op_num == 2 + +def test_atomicalsconsensus_OP_DECODEBLOCKINFO_deploy_success_items(): + block_header = '000000209174c9f2757e2647733c9ab69c133b90257f85ce7e9c0100000000000000000096e490f16d161416bef825bd4716b0914f344be5835cf6a8f0de89288a6c440391af0566d3620317aca8414a' + block_header_encoded = encode_op_pushdata(bytes.fromhex(block_header)) + block_items = [ + { + 'field': 0, + 'field_enc': '00', + 'result': '0400000020' + }, + { + 'field': 1, + 'field_enc': '51', + 'result': '20' + '9174c9f2757e2647733c9ab69c133b90257f85ce7e9c01000000000000000000' + }, + { + 'field': 2, + 'field_enc': '52', + 'result': '20' + '96e490f16d161416bef825bd4716b0914f344be5835cf6a8f0de89288a6c4403' + }, + { + 'field': 3, + 'field_enc': '53', + 'result': encode_int_value(1711648657).hex() + }, + { + 'field': 4, + 'field_enc': '54', + 'result': encode_int_value(386097875).hex(), + }, + { + 'field': 5, + 'field_enc': '55', + 'result': encode_int_value(1245817004).hex() + }, + { + 'field': 6, + 'field_enc': '56', + 'result': encode_int_value(83126997340025).hex() + } + ] + for item in block_items: + lock_script = 'fc' + item['result'] + '87' + protocol_mint_data = { + 'p': 'ppp', + 'code': bytes.fromhex(lock_script), + 'fn': [ + { + 'name': 'ctor', + 'params': [ + { + 'name': 'header', + 'type': 'bytes' + }, + { + 'name': 'field', + 'type': 'int' + } + ] + } + ] + } + deploy_payload = { + 'p': 'ppp', + 'u': bytes.fromhex(block_header_encoded.hex() + item['field_enc']), + 'args': { + } + } + avm = AVMFactory(MockLogger(), mock_mint_fetcher, mock_blockchain_context, protocol_mint_data) + atomicals_spent_at_inputs = {} + request_tx_context = RequestTxContext(coin, mock_tx_hash, mock_tx, deploy_payload) + deploy_command = avm.create_deploy_command(request_tx_context, atomicals_spent_at_inputs, mock_empty_reactor_context) + assert(deploy_command.is_valid) + assert(deploy_command.unlock_script.hex() == block_header_encoded.hex() + item['field_enc']) + assert(deploy_command.lock_script.hex() == lock_script) + result = deploy_command.execute() + assert result.success + \ No newline at end of file diff --git a/tests/lib/test_atomicalsconsensus_OP_FT_BALANCE.py b/tests/lib/test_atomicalsconsensus_OP_FT_BALANCE.py new file mode 100644 index 00000000..43ab80be --- /dev/null +++ b/tests/lib/test_atomicalsconsensus_OP_FT_BALANCE.py @@ -0,0 +1,108 @@ +import pytest + +from electrumx.lib.coins import Bitcoin +from electrumx.lib.hash import hex_str_to_hash + +from electrumx.lib.avm.util import ( + encode_int_value, + RequestInterpretParams, + RequestBlockchainContext, + print_result_states, + ScriptContext, + encode_op_pushdata +) + +from electrumx.lib.avm.avm import ( + RequestBlockchainContext, + RequestTxContext, + ReactorContext +) +from bitcointx.core.atomicalsconsensus import ( + ConsensusVerifyScriptAvmExecute, + AtomicalConsensusExecutionError +) + +from bitcointx.core.script import ( + CScript +) + +from cbor2 import dumps + +coin = Bitcoin + +class MockLogger: + def debug(self, msg): + return + def info(self, msg): + return + def warning(self, msg): + return + +mock_current_header = '000000209174c9f2757e2647733c9ab69c133b90257f85ce7e9c0100000000000000000096e490f16d161416bef825bd4716b0914f344be5835cf6a8f0de89288a6c440391af0566d3620317aca8414a' +mock_headers = { + '840012': mock_current_header +} +mock_blockchain_context = RequestBlockchainContext(mock_headers, 840012) +mock_rawtx = bytes.fromhex('02000000018e469f953413e8d865fcf1f47d759772aa05e8d78b1e4577a58edc8bd09344ff010000006b483045022100ce16646785907c919a1658496a85cf3f3d877d98ffca5eabd189524acc1de53b02205ac24a9e2855db3da26b840c82fddaf73a5a6eff4b5b78cadfb00584ad6bd5f3012102cbcad7b21fb5fb08ad55eb09e327b97f63e8c5e99b2faf9bb330545a5bd4602cfeffffff0203761700000000001976a91496d02c013f734a642871261324c58091a806c23188ac9ce52300000000001976a914a9a8d5aa3ec73688d0540d45be3842f4603705c588ac6e640800') +mock_tx, mock_tx_hash = coin.DESERIALIZER(mock_rawtx, 0).read_tx_and_hash() +mock_empty_reactor_context = ReactorContext(None, dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) + +def test_atomicalsconsensus_OP_FT_BALANCE_empty_success_zero1(): + payload = {} + sample_token_id1 = '012345678901234567890123456789012345678901234567890123456789000000000000' + sample_token_id1_bytes = bytearray.fromhex(sample_token_id1) + sample_token_id1_bytes.reverse() + sample_token_id1_encoded = encode_op_pushdata(sample_token_id1_bytes) + state_hash = bytes.fromhex('0000000000000000000000000000000000000000000000000000000000000000') + request_tx_context = RequestTxContext(coin, mock_tx_hash, mock_tx, payload) + reactor_context = ReactorContext(state_hash, dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) + blockchain_context = RequestBlockchainContext(mock_headers, 840012) + script_context = ScriptContext(CScript(), CScript(bytes.fromhex(sample_token_id1_encoded.hex() + '00f40087'))) + updated_reactor_state = ConsensusVerifyScriptAvmExecute(script_context, blockchain_context, request_tx_context, reactor_context) + assert updated_reactor_state + +def test_atomicalsconsensus_OP_FT_BALANCE_non_zero1(): + balances = {} + sample_token_id1 = '012345678901234567890123456789012345678901234567890123456789000000000000' + sample_token_id1_bytes = bytearray.fromhex(sample_token_id1) + sample_token_id1_bytes.reverse() + sample_token_id1_encoded = encode_op_pushdata(sample_token_id1_bytes) + balances[sample_token_id1] = 1 + state_hash = bytes.fromhex('0000000000000000000000000000000000000000000000000000000000000000') + reactor_context = ReactorContext(state_hash, dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps(balances), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) + payload = {} + request_tx_context = RequestTxContext(coin, mock_tx_hash, mock_tx, payload) + blockchain_context = RequestBlockchainContext(mock_headers, 840012) + script_context = ScriptContext(CScript(), CScript(bytes.fromhex(sample_token_id1_encoded.hex() + '00f45187'))) + updated_reactor_state = ConsensusVerifyScriptAvmExecute(script_context, blockchain_context, request_tx_context, reactor_context) + assert updated_reactor_state + +def test_atomicalsconsensus_OP_FT_BALANCE_incoming_empty_success_zero1(): + payload = {} + sample_token_id1 = '012345678901234567890123456789012345678901234567890123456789000000000000' + sample_token_id1_bytes = bytearray.fromhex(sample_token_id1) + sample_token_id1_bytes.reverse() + sample_token_id1_encoded = encode_op_pushdata(sample_token_id1_bytes) + state_hash = bytes.fromhex('0000000000000000000000000000000000000000000000000000000000000000') + request_tx_context = RequestTxContext(coin, mock_tx_hash, mock_tx, payload) + reactor_context = ReactorContext(state_hash, dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) + blockchain_context = RequestBlockchainContext(mock_headers, 840012) + script_context = ScriptContext(CScript(), CScript(bytes.fromhex(sample_token_id1_encoded.hex() + '00f40087'))) + updated_reactor_state = ConsensusVerifyScriptAvmExecute(script_context, blockchain_context, request_tx_context, reactor_context) + assert updated_reactor_state + +def test_atomicalsconsensus_OP_FT_BALANCE_incoming_non_zero2(): + balances = {} + sample_token_id1 = '012345678901234567890123456789012345678901234567890123456789000000000000' + sample_token_id1_bytes = bytearray.fromhex(sample_token_id1) + sample_token_id1_bytes.reverse() + sample_token_id1_encoded = encode_op_pushdata(sample_token_id1_bytes) + balances[sample_token_id1] = 1 + state_hash = bytes.fromhex('0000000000000000000000000000000000000000000000000000000000000000') + reactor_context = ReactorContext(state_hash, dumps({}), dumps({}), dumps({}), dumps({}), dumps(balances), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) + payload = {} + request_tx_context = RequestTxContext(coin, mock_tx_hash, mock_tx, payload) + blockchain_context = RequestBlockchainContext(mock_headers, 840012) + script_context = ScriptContext(CScript(), CScript(bytes.fromhex(sample_token_id1_encoded.hex() + '51f45187'))) + updated_reactor_state = ConsensusVerifyScriptAvmExecute(script_context, blockchain_context, request_tx_context, reactor_context) + assert updated_reactor_state \ No newline at end of file diff --git a/tests/lib/test_atomicalsconsensus_OP_FT_BALANCE_ADD.py b/tests/lib/test_atomicalsconsensus_OP_FT_BALANCE_ADD.py new file mode 100644 index 00000000..a04e5ed0 --- /dev/null +++ b/tests/lib/test_atomicalsconsensus_OP_FT_BALANCE_ADD.py @@ -0,0 +1,288 @@ +import pytest + +from electrumx.lib.coins import Bitcoin +from electrumx.lib.hash import hex_str_to_hash + +from electrumx.lib.avm.util import ( + encode_int_value, + RequestInterpretParams, + RequestBlockchainContext, + print_result_states, + ScriptContext, + encode_op_pushdata +) + +from electrumx.lib.avm.avm import ( + RequestBlockchainContext, + RequestTxContext, + ReactorContext +) +from bitcointx.core.atomicalsconsensus import ( + ConsensusVerifyScriptAvmExecute, + AtomicalConsensusExecutionError +) + +from bitcointx.core.script import ( + CScript +) + +from cbor2 import dumps, loads + +coin = Bitcoin + +class MockLogger: + def debug(self, msg): + return + def info(self, msg): + return + def warning(self, msg): + return + +mock_current_header = '000000209174c9f2757e2647733c9ab69c133b90257f85ce7e9c0100000000000000000096e490f16d161416bef825bd4716b0914f344be5835cf6a8f0de89288a6c440391af0566d3620317aca8414a' +mock_headers = { + '840012': mock_current_header +} +mock_blockchain_context = RequestBlockchainContext(mock_headers, 840012) +mock_rawtx = bytes.fromhex('02000000018e469f953413e8d865fcf1f47d759772aa05e8d78b1e4577a58edc8bd09344ff010000006b483045022100ce16646785907c919a1658496a85cf3f3d877d98ffca5eabd189524acc1de53b02205ac24a9e2855db3da26b840c82fddaf73a5a6eff4b5b78cadfb00584ad6bd5f3012102cbcad7b21fb5fb08ad55eb09e327b97f63e8c5e99b2faf9bb330545a5bd4602cfeffffff0203761700000000001976a91496d02c013f734a642871261324c58091a806c23188ac9ce52300000000001976a914a9a8d5aa3ec73688d0540d45be3842f4603705c588ac6e640800') +mock_tx, mock_tx_hash = coin.DESERIALIZER(mock_rawtx, 0).read_tx_and_hash() +mock_empty_reactor_context = ReactorContext(None, dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) + +def test_atomicalsconsensus_OP_FT_BALANCE_ADD_zero_amount_fail(): + with pytest.raises(AtomicalConsensusExecutionError) as exc: + payload = {} + balances = {} + sample_token_id1 = '012345678901234567890123456789012345678901234567890123456789000000000000' + sample_token_id1_bytes = bytearray.fromhex(sample_token_id1) + sample_token_id1_bytes.reverse() + sample_token_id1_encoded = encode_op_pushdata(sample_token_id1_bytes) + sample_token_id2 = '712345678901234567890123456789012345678901234567890123456789000000000002' + sample_token_id2_bytes = bytearray.fromhex(sample_token_id2) + sample_token_id2_bytes.reverse() + sample_token_id2_encoded = encode_op_pushdata(sample_token_id2_bytes) + balances[sample_token_id1] = 1 + state_hash = bytes.fromhex('0000000000000000000000000000000000000000000000000000000000000000') + request_tx_context = RequestTxContext(coin, mock_tx_hash, mock_tx, payload) + reactor_context = ReactorContext(state_hash, dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps(balances), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) + blockchain_context = RequestBlockchainContext(mock_headers, 840012) + script_context = ScriptContext(CScript(), CScript(bytes.fromhex(sample_token_id2_encoded.hex() + 'd3'))) + updated_reactor_state = ConsensusVerifyScriptAvmExecute(script_context, blockchain_context, request_tx_context, reactor_context) + assert updated_reactor_state + assert exc.value.error_code == 0 + assert exc.value.script_error == 81 + assert exc.value.script_error_op_num == 1 + +def test_atomicalsconsensus_OP_FT_BALANCE_ADD_no_incoming_available_fail(): + with pytest.raises(AtomicalConsensusExecutionError) as exc: + payload = {} + balances = {} + sample_token_id1 = '012345678901234567890123456789012345678901234567890123456789000000000000' + sample_token_id1_bytes = bytearray.fromhex(sample_token_id1) + sample_token_id1_bytes.reverse() + sample_token_id1_encoded = encode_op_pushdata(sample_token_id1_bytes) + sample_token_id2 = '712345678901234567890123456789012345678901234567890123456789000000000002' + sample_token_id2_bytes = bytearray.fromhex(sample_token_id2) + sample_token_id2_bytes.reverse() + sample_token_id2_encoded = encode_op_pushdata(sample_token_id2_bytes) + balances[sample_token_id1] = 1 + state_hash = bytes.fromhex('0000000000000000000000000000000000000000000000000000000000000000') + request_tx_context = RequestTxContext(coin, mock_tx_hash, mock_tx, payload) + reactor_context = ReactorContext(state_hash, dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps(balances), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) + blockchain_context = RequestBlockchainContext(mock_headers, 840012) + script_context = ScriptContext(CScript(), CScript(bytes.fromhex(sample_token_id2_encoded.hex() + 'd3'))) + updated_reactor_state = ConsensusVerifyScriptAvmExecute(script_context, blockchain_context, request_tx_context, reactor_context) + assert updated_reactor_state + assert exc.value.error_code == 0 + assert exc.value.script_error == 81 + assert exc.value.script_error_op_num == 1 + +def test_atomicalsconsensus_OP_FT_BALANCE_ADD_incoming_insufficient_fail(): + with pytest.raises(AtomicalConsensusExecutionError) as exc: + payload = {} + balances = {} + balances_incoming = {} + sample_token_id1 = '012345678901234567890123456789012345678901234567890123456789000000000000' + sample_token_id1_bytes = bytearray.fromhex(sample_token_id1) + sample_token_id1_bytes.reverse() + sample_token_id1_encoded = encode_op_pushdata(sample_token_id1_bytes) + sample_token_id2 = '712345678901234567890123456789012345678901234567890123456789000000000002' + sample_token_id2_bytes = bytearray.fromhex(sample_token_id2) + sample_token_id2_bytes.reverse() + sample_token_id2_encoded = encode_op_pushdata(sample_token_id2_bytes) + balances[sample_token_id1] = 1 + balances_incoming[sample_token_id1] = 1 + state_hash = bytes.fromhex('0000000000000000000000000000000000000000000000000000000000000000') + request_tx_context = RequestTxContext(coin, mock_tx_hash, mock_tx, payload) + reactor_context = ReactorContext(state_hash, dumps({}), dumps({}), dumps({}), dumps({}), dumps(balances_incoming), dumps({}), dumps({}), dumps(balances), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) + blockchain_context = RequestBlockchainContext(mock_headers, 840012) + script_context = ScriptContext(CScript(), CScript(bytes.fromhex(sample_token_id2_encoded.hex() + 'd3'))) + updated_reactor_state = ConsensusVerifyScriptAvmExecute(script_context, blockchain_context, request_tx_context, reactor_context) + assert updated_reactor_state + assert exc.value.error_code == 0 + assert exc.value.script_error == 81 + assert exc.value.script_error_op_num == 1 + +def test_atomicalsconsensus_OP_FT_BALANCE_ADD_incoming_new_success(): + payload = {} + balances = {} + balances_incoming = {} + sample_token_id1 = '012345678901234567890123456789012345678901234567890123456789000000000000' + sample_token_id1_bytes = bytearray.fromhex(sample_token_id1) + sample_token_id1_bytes.reverse() + sample_token_id1_encoded = encode_op_pushdata(sample_token_id1_bytes) + sample_token_id2 = '712345678901234567890123456789012345678901234567890123456789000000000002' + sample_token_id2_bytes = bytearray.fromhex(sample_token_id2) + sample_token_id2_bytes.reverse() + sample_token_id2_encoded = encode_op_pushdata(sample_token_id2_bytes) + balances[sample_token_id1] = 1 + balances_incoming[sample_token_id1] = 1 + state_hash = bytes.fromhex('0000000000000000000000000000000000000000000000000000000000000000') + request_tx_context = RequestTxContext(coin, mock_tx_hash, mock_tx, payload) + reactor_context = ReactorContext(state_hash, dumps({}), dumps({}), dumps({}), dumps({}), dumps(balances_incoming), dumps({}), dumps({}), dumps(balances), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) + blockchain_context = RequestBlockchainContext(mock_headers, 840012) + script_context = ScriptContext(CScript(), CScript(bytes.fromhex(sample_token_id1_encoded.hex() + 'd351'))) + updated_reactor_state = ConsensusVerifyScriptAvmExecute(script_context, blockchain_context, request_tx_context, reactor_context) + assert updated_reactor_state + expected_ft_balances = {} + expected_ft_balances[sample_token_id1] = 2 + assert updated_reactor_state.ft_balances == dumps(expected_ft_balances) + + print(loads(updated_reactor_state.state)) + assert len(loads(updated_reactor_state.state)) == 0 + assert len(loads(updated_reactor_state.state_updates)) == 0 + assert len(loads(updated_reactor_state.state_deletes)) == 0 + assert len(loads(updated_reactor_state.ft_incoming)) == 1 + assert len(loads(updated_reactor_state.nft_incoming)) == 0 + assert len(loads(updated_reactor_state.ft_balances)) == 1 + assert len(loads(updated_reactor_state.ft_adds)) == 1 + assert len(loads(updated_reactor_state.nft_puts)) == 0 + expected_ft_adds = {} + expected_ft_adds[sample_token_id1] = True + assert updated_reactor_state.ft_adds == dumps(expected_ft_adds) + + expected_ft_balances_updates = {} + expected_ft_balances_updates[sample_token_id1] = 2 + assert updated_reactor_state.ft_balances_updates == dumps(expected_ft_balances_updates) + + assert len(loads(updated_reactor_state.ft_balances_updates)) == 1 + assert len(loads(updated_reactor_state.nft_balances)) == 0 + assert len(loads(updated_reactor_state.nft_balances_updates)) == 0 + assert len(loads(updated_reactor_state.ft_withdraws)) == 0 + assert len(loads(updated_reactor_state.nft_withdraws)) == 0 + +def test_atomicalsconsensus_OP_FT_BALANCE_ADD_incoming_max_int_test(): + max_int = 9223372036854775807 + payload = {} + balances = {} + balances_incoming = {} + sample_token_id1 = '012345678901234567890123456789012345678901234567890123456789000000000000' + sample_token_id1_bytes = bytearray.fromhex(sample_token_id1) + sample_token_id1_bytes.reverse() + sample_token_id1_encoded = encode_op_pushdata(sample_token_id1_bytes) + sample_token_id2 = '712345678901234567890123456789012345678901234567890123456789000000000002' + sample_token_id2_bytes = bytearray.fromhex(sample_token_id2) + sample_token_id2_bytes.reverse() + sample_token_id2_encoded = encode_op_pushdata(sample_token_id2_bytes) + balances[sample_token_id1] = (max_int - 1) + balances_incoming[sample_token_id1] = 1 + state_hash = bytes.fromhex('0000000000000000000000000000000000000000000000000000000000000000') + request_tx_context = RequestTxContext(coin, mock_tx_hash, mock_tx, payload) + reactor_context = ReactorContext(state_hash, dumps({}), dumps({}), dumps({}), dumps({}), dumps(balances_incoming), dumps({}), dumps({}), dumps(balances), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) + blockchain_context = RequestBlockchainContext(mock_headers, 840012) + script_context = ScriptContext(CScript(), CScript(bytes.fromhex(sample_token_id1_encoded.hex() + 'd351'))) + updated_reactor_state = ConsensusVerifyScriptAvmExecute(script_context, blockchain_context, request_tx_context, reactor_context) + assert updated_reactor_state + expected_ft_balances = {} + expected_ft_balances[sample_token_id1] = max_int + assert updated_reactor_state.ft_balances == dumps(expected_ft_balances) + print(loads(updated_reactor_state.state)) + assert len(loads(updated_reactor_state.state)) == 0 + assert len(loads(updated_reactor_state.state_updates)) == 0 + assert len(loads(updated_reactor_state.state_deletes)) == 0 + assert len(loads(updated_reactor_state.ft_incoming)) == 1 + assert len(loads(updated_reactor_state.nft_incoming)) == 0 + assert len(loads(updated_reactor_state.ft_balances)) == 1 + expected_ft_balances_updates = {} + expected_ft_balances_updates[sample_token_id1] = max_int + assert updated_reactor_state.ft_balances_updates == dumps(expected_ft_balances_updates) + assert len(loads(updated_reactor_state.ft_balances_updates)) == 1 + assert len(loads(updated_reactor_state.nft_balances)) == 0 + assert len(loads(updated_reactor_state.nft_balances_updates)) == 0 + assert len(loads(updated_reactor_state.ft_withdraws)) == 0 + assert len(loads(updated_reactor_state.nft_withdraws)) == 0 + assert len(loads(updated_reactor_state.ft_adds)) == 1 + assert len(loads(updated_reactor_state.nft_puts)) == 0 + expected_ft_adds = {} + expected_ft_adds[sample_token_id1] = True + assert updated_reactor_state.ft_adds == dumps(expected_ft_adds) + + +def test_atomicalsconsensus_OP_FT_BALANCE_ADD_incoming_multiple_adds_fail(): + with pytest.raises(AtomicalConsensusExecutionError) as exc: + payload = {} + balances = {} + balances_incoming = {} + sample_token_id1 = '012345678901234567890123456789012345678901234567890123456789000000000000' + sample_token_id1_bytes = bytearray.fromhex(sample_token_id1) + sample_token_id1_bytes.reverse() + sample_token_id1_encoded = encode_op_pushdata(sample_token_id1_bytes) + sample_token_id2 = '712345678901234567890123456789012345678901234567890123456789000000000002' + sample_token_id2_bytes = bytearray.fromhex(sample_token_id2) + sample_token_id2_bytes.reverse() + sample_token_id2_encoded = encode_op_pushdata(sample_token_id2_bytes) + balances[sample_token_id1] = 1 + balances_incoming[sample_token_id1] = 1 + state_hash = bytes.fromhex('0000000000000000000000000000000000000000000000000000000000000000') + request_tx_context = RequestTxContext(coin, mock_tx_hash, mock_tx, payload) + reactor_context = ReactorContext(state_hash, dumps({}), dumps({}), dumps({}), dumps({}), dumps(balances_incoming), dumps({}), dumps({}), dumps(balances), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) + blockchain_context = RequestBlockchainContext(mock_headers, 840012) + script_context = ScriptContext(CScript(), CScript(bytes.fromhex(sample_token_id1_encoded.hex() + 'd3' + sample_token_id1_encoded.hex() + 'd3'))) + ConsensusVerifyScriptAvmExecute(script_context, blockchain_context, request_tx_context, reactor_context) + + assert exc.value.error_code == 0 + assert exc.value.script_error == 81 + assert exc.value.script_error_op_num == 3 + +def test_atomicalsconsensus_OP_FT_BALANCE_ADD_then_OP_FT_WITHDRAW_immediately(): + max_int = 9223372036854775807 + payload = {} + balances = {} + balances_incoming = {} + sample_token_id1 = '012345678901234567890123456789012345678901234567890123456789000000000000' + sample_token_id1_bytes = bytearray.fromhex(sample_token_id1) + sample_token_id1_bytes.reverse() + sample_token_id1_encoded = encode_op_pushdata(sample_token_id1_bytes) + sample_token_id2 = '712345678901234567890123456789012345678901234567890123456789000000000002' + sample_token_id2_bytes = bytearray.fromhex(sample_token_id2) + sample_token_id2_bytes.reverse() + balances_incoming[sample_token_id1] = 2 + state_hash = bytes.fromhex('0000000000000000000000000000000000000000000000000000000000000000') + request_tx_context = RequestTxContext(coin, mock_tx_hash, mock_tx, payload) + reactor_context = ReactorContext(state_hash, dumps({}), dumps({}), dumps({}), dumps({}), dumps(balances_incoming), dumps({}), dumps({}), dumps(balances), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) + blockchain_context = RequestBlockchainContext(mock_headers, 840012) + script_context = ScriptContext(CScript(), CScript(bytes.fromhex(sample_token_id1_encoded.hex() + 'd3' + sample_token_id1_encoded.hex() + '0051f2' + sample_token_id1_encoded.hex() + '5151f2' + '51'))) + updated_reactor_state = ConsensusVerifyScriptAvmExecute(script_context, blockchain_context, request_tx_context, reactor_context) + assert updated_reactor_state + expected_ft_balances = {} + assert updated_reactor_state.ft_balances == dumps(expected_ft_balances) + assert len(loads(updated_reactor_state.state)) == 0 + assert len(loads(updated_reactor_state.state_updates)) == 0 + assert len(loads(updated_reactor_state.state_deletes)) == 0 + assert len(loads(updated_reactor_state.ft_incoming)) == 1 + assert len(loads(updated_reactor_state.nft_incoming)) == 0 + assert len(loads(updated_reactor_state.ft_balances)) == 0 + expected_ft_balances_updates = {} + expected_ft_balances_updates[sample_token_id1] = 0 + assert updated_reactor_state.ft_balances_updates == dumps(expected_ft_balances_updates) + assert len(loads(updated_reactor_state.ft_balances_updates)) == 1 + assert len(loads(updated_reactor_state.nft_balances)) == 0 + assert len(loads(updated_reactor_state.nft_balances_updates)) == 0 + decoded_withdraws = loads(updated_reactor_state.ft_withdraws) + assert len(decoded_withdraws[sample_token_id1]) == 2 + assert decoded_withdraws[sample_token_id1]['0'] == 1 + assert decoded_withdraws[sample_token_id1]['1'] == 1 + assert len(loads(updated_reactor_state.nft_withdraws)) == 0 + assert len(loads(updated_reactor_state.ft_adds)) == 1 + assert len(loads(updated_reactor_state.nft_puts)) == 0 + expected_ft_adds = {} + expected_ft_adds[sample_token_id1] = True + assert updated_reactor_state.ft_adds == dumps(expected_ft_adds) \ No newline at end of file diff --git a/tests/lib/test_atomicalsconsensus_OP_FT_COUNT.py b/tests/lib/test_atomicalsconsensus_OP_FT_COUNT.py new file mode 100644 index 00000000..db86f841 --- /dev/null +++ b/tests/lib/test_atomicalsconsensus_OP_FT_COUNT.py @@ -0,0 +1,123 @@ +import pytest + +from electrumx.lib.coins import Bitcoin +from electrumx.lib.hash import hex_str_to_hash + +from electrumx.lib.avm.util import ( + encode_int_value, + RequestInterpretParams, + RequestBlockchainContext, + print_result_states, + ScriptContext +) + +from electrumx.lib.avm.avm import ( + RequestBlockchainContext, + RequestTxContext, + ReactorContext +) +from bitcointx.core.atomicalsconsensus import ( + ConsensusVerifyScriptAvmExecute +) + +from bitcointx.core.script import ( + CScript +) + +from cbor2 import dumps + +coin = Bitcoin + +class MockLogger: + def debug(self, msg): + return + def info(self, msg): + return + def warning(self, msg): + return + +mock_current_header = '000000209174c9f2757e2647733c9ab69c133b90257f85ce7e9c0100000000000000000096e490f16d161416bef825bd4716b0914f344be5835cf6a8f0de89288a6c440391af0566d3620317aca8414a' +mock_headers = { + '840012': mock_current_header +} +mock_blockchain_context = RequestBlockchainContext(mock_headers, 840012) +mock_rawtx = bytes.fromhex('02000000018e469f953413e8d865fcf1f47d759772aa05e8d78b1e4577a58edc8bd09344ff010000006b483045022100ce16646785907c919a1658496a85cf3f3d877d98ffca5eabd189524acc1de53b02205ac24a9e2855db3da26b840c82fddaf73a5a6eff4b5b78cadfb00584ad6bd5f3012102cbcad7b21fb5fb08ad55eb09e327b97f63e8c5e99b2faf9bb330545a5bd4602cfeffffff0203761700000000001976a91496d02c013f734a642871261324c58091a806c23188ac9ce52300000000001976a914a9a8d5aa3ec73688d0540d45be3842f4603705c588ac6e640800') +mock_tx, mock_tx_hash = coin.DESERIALIZER(mock_rawtx, 0).read_tx_and_hash() +mock_empty_reactor_context = ReactorContext(None, dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) + +def test_atomicalsconsensus_OP_FT_COUNT_empty_success1(): + payload = {} + state_hash = bytes.fromhex('0000000000000000000000000000000000000000000000000000000000000000') + request_tx_context = RequestTxContext(coin, mock_tx_hash, mock_tx, payload) + reactor_context = ReactorContext(state_hash, dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) + blockchain_context = RequestBlockchainContext(mock_headers, 840012) + script_context = ScriptContext(CScript(), CScript(bytes.fromhex('00f60087'))) + updated_reactor_state = ConsensusVerifyScriptAvmExecute(script_context, blockchain_context, request_tx_context, reactor_context) + assert updated_reactor_state + +def test_atomicalsconsensus_OP_FT_COUNT_nonempty_success1(): + balances = {} + sample_ft_token_id1 = '012345678901234567890123456789012345678901234567890123456789000000000000' + balances[sample_ft_token_id1] = 22 + state_hash = bytes.fromhex('0000000000000000000000000000000000000000000000000000000000000000') + reactor_context = ReactorContext(state_hash, dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps(balances), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) + payload = {} + request_tx_context = RequestTxContext(coin, mock_tx_hash, mock_tx, payload) + blockchain_context = RequestBlockchainContext(mock_headers, 840012) + script_context = ScriptContext(CScript(), CScript(bytes.fromhex('00f65187'))) + updated_reactor_state = ConsensusVerifyScriptAvmExecute(script_context, blockchain_context, request_tx_context, reactor_context) + assert updated_reactor_state + +def test_atomicalsconsensus_OP_FT_COUNT_nonempty_success2(): + balances = {} + sample_ft_token_id1 = '012345678901234567890123456789012345678901234567890123456789000000000000' + sample_ft_token_id2 = '112345678901234567890123456789012345678901234567890123456789000000000001' + balances[sample_ft_token_id1] = 22 + balances[sample_ft_token_id2] = 1 + state_hash = bytes.fromhex('0000000000000000000000000000000000000000000000000000000000000000') + reactor_context = ReactorContext(state_hash, dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps(balances), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) + payload = {} + request_tx_context = RequestTxContext(coin, mock_tx_hash, mock_tx, payload) + blockchain_context = RequestBlockchainContext(mock_headers, 840012) + script_context = ScriptContext(CScript(), CScript(bytes.fromhex('00f65287'))) + updated_reactor_state = ConsensusVerifyScriptAvmExecute(script_context, blockchain_context, request_tx_context, reactor_context) + assert updated_reactor_state + + +def test_atomicalsconsensus_OP_FT_COUNT_incoming_empty_success1(): + payload = {} + state_hash = bytes.fromhex('0000000000000000000000000000000000000000000000000000000000000000') + request_tx_context = RequestTxContext(coin, mock_tx_hash, mock_tx, payload) + reactor_context = ReactorContext(state_hash, dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) + blockchain_context = RequestBlockchainContext(mock_headers, 840012) + script_context = ScriptContext(CScript(), CScript(bytes.fromhex('51f60087'))) + updated_reactor_state = ConsensusVerifyScriptAvmExecute(script_context, blockchain_context, request_tx_context, reactor_context) + assert updated_reactor_state + +def test_atomicalsconsensus_OP_FT_COUNT_incoming_nonempty_success1(): + balances = {} + sample_ft_token_id1 = '012345678901234567890123456789012345678901234567890123456789000000000000' + balances[sample_ft_token_id1] = 22 + state_hash = bytes.fromhex('0000000000000000000000000000000000000000000000000000000000000000') + reactor_context = ReactorContext(state_hash, dumps({}), dumps({}), dumps({}), dumps({}), dumps(balances), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) + payload = {} + request_tx_context = RequestTxContext(coin, mock_tx_hash, mock_tx, payload) + blockchain_context = RequestBlockchainContext(mock_headers, 840012) + script_context = ScriptContext(CScript(), CScript(bytes.fromhex('51f65187'))) + updated_reactor_state = ConsensusVerifyScriptAvmExecute(script_context, blockchain_context, request_tx_context, reactor_context) + assert updated_reactor_state + +def test_atomicalsconsensus_OP_FT_COUNT_incoming_nonempty_success2(): + balances = {} + sample_ft_token_id1 = '012345678901234567890123456789012345678901234567890123456789000000000000' + sample_ft_token_id2 = '112345678901234567890123456789012345678901234567890123456789000000000001' + balances[sample_ft_token_id1] = 22 + balances[sample_ft_token_id2] = 1 + state_hash = bytes.fromhex('0000000000000000000000000000000000000000000000000000000000000000') + reactor_context = ReactorContext(state_hash, dumps({}), dumps({}), dumps({}), dumps({}), dumps(balances), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) + payload = {} + request_tx_context = RequestTxContext(coin, mock_tx_hash, mock_tx, payload) + blockchain_context = RequestBlockchainContext(mock_headers, 840012) + script_context = ScriptContext(CScript(), CScript(bytes.fromhex('51f65287'))) + updated_reactor_state = ConsensusVerifyScriptAvmExecute(script_context, blockchain_context, request_tx_context, reactor_context) + assert updated_reactor_state \ No newline at end of file diff --git a/tests/lib/test_atomicalsconsensus_OP_FT_ITEM.py b/tests/lib/test_atomicalsconsensus_OP_FT_ITEM.py new file mode 100644 index 00000000..5682e712 --- /dev/null +++ b/tests/lib/test_atomicalsconsensus_OP_FT_ITEM.py @@ -0,0 +1,144 @@ +import pytest + +from electrumx.lib.coins import Bitcoin +from electrumx.lib.hash import hex_str_to_hash + +from electrumx.lib.avm.util import ( + encode_int_value, + RequestInterpretParams, + RequestBlockchainContext, + print_result_states, + ScriptContext, + encode_op_pushdata +) + +from electrumx.lib.avm.avm import ( + RequestBlockchainContext, + RequestTxContext, + ReactorContext +) +from bitcointx.core.atomicalsconsensus import ( + ConsensusVerifyScriptAvmExecute, + AtomicalConsensusExecutionError +) + +from bitcointx.core.script import ( + CScript +) + +from cbor2 import dumps + +coin = Bitcoin + +class MockLogger: + def debug(self, msg): + return + def info(self, msg): + return + def warning(self, msg): + return + +mock_current_header = '000000209174c9f2757e2647733c9ab69c133b90257f85ce7e9c0100000000000000000096e490f16d161416bef825bd4716b0914f344be5835cf6a8f0de89288a6c440391af0566d3620317aca8414a' +mock_headers = { + '840012': mock_current_header +} +mock_blockchain_context = RequestBlockchainContext(mock_headers, 840012) +mock_rawtx = bytes.fromhex('02000000018e469f953413e8d865fcf1f47d759772aa05e8d78b1e4577a58edc8bd09344ff010000006b483045022100ce16646785907c919a1658496a85cf3f3d877d98ffca5eabd189524acc1de53b02205ac24a9e2855db3da26b840c82fddaf73a5a6eff4b5b78cadfb00584ad6bd5f3012102cbcad7b21fb5fb08ad55eb09e327b97f63e8c5e99b2faf9bb330545a5bd4602cfeffffff0203761700000000001976a91496d02c013f734a642871261324c58091a806c23188ac9ce52300000000001976a914a9a8d5aa3ec73688d0540d45be3842f4603705c588ac6e640800') +mock_tx, mock_tx_hash = coin.DESERIALIZER(mock_rawtx, 0).read_tx_and_hash() +mock_empty_reactor_context = ReactorContext(None, dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) + +def test_atomicalsconsensus_OP_FT_ITEM_out_of_bounds(): + with pytest.raises(AtomicalConsensusExecutionError) as exc: + payload = {} + state_hash = bytes.fromhex('0000000000000000000000000000000000000000000000000000000000000000') + request_tx_context = RequestTxContext(coin, mock_tx_hash, mock_tx, payload) + reactor_context = ReactorContext(state_hash, dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) + blockchain_context = RequestBlockchainContext(mock_headers, 840012) + script_context = ScriptContext(CScript(), CScript(bytes.fromhex('0000f70087'))) + updated_reactor_state = ConsensusVerifyScriptAvmExecute(script_context, blockchain_context, request_tx_context, reactor_context) + assert updated_reactor_state + assert exc.value.error_code == 0 + assert exc.value.script_error == 67 + assert exc.value.script_error_op_num == 2 + +def test_atomicalsconsensus_OP_FT_ITEM_incoming_out_of_bounds(): + with pytest.raises(AtomicalConsensusExecutionError) as exc: + payload = {} + state_hash = bytes.fromhex('0000000000000000000000000000000000000000000000000000000000000000') + request_tx_context = RequestTxContext(coin, mock_tx_hash, mock_tx, payload) + reactor_context = ReactorContext(state_hash, dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) + blockchain_context = RequestBlockchainContext(mock_headers, 840012) + script_context = ScriptContext(CScript(), CScript(bytes.fromhex('5251f70087'))) + updated_reactor_state = ConsensusVerifyScriptAvmExecute(script_context, blockchain_context, request_tx_context, reactor_context) + assert updated_reactor_state + assert exc.value.error_code == 0 + assert exc.value.script_error == 67 + assert exc.value.script_error_op_num == 2 + +def test_atomicalsconsensus_OP_FT_ITEM_success1(): + balances = {} + sample_token_id1 = '012345678901234567890123456789012345678901234567890123456789000000000000' + sample_token_id1_bytes = bytearray.fromhex(sample_token_id1) + sample_token_id1_bytes.reverse() + sample_token_id1_encoded = encode_op_pushdata(sample_token_id1_bytes) + balances[sample_token_id1] = 1 + state_hash = bytes.fromhex('0000000000000000000000000000000000000000000000000000000000000000') + reactor_context = ReactorContext(state_hash, dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps(balances), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) + payload = {} + request_tx_context = RequestTxContext(coin, mock_tx_hash, mock_tx, payload) + blockchain_context = RequestBlockchainContext(mock_headers, 840012) + script_context = ScriptContext(CScript(), CScript(bytes.fromhex('0000f7' + sample_token_id1_encoded.hex() + '87'))) + updated_reactor_state = ConsensusVerifyScriptAvmExecute(script_context, blockchain_context, request_tx_context, reactor_context) + assert updated_reactor_state + +def test_atomicalsconsensus_OP_FT_ITEM_success2(): + balances = {} + sample_ft_token_id1 = '012345678901234567890123456789012345678901234567890123456789000000000000' + sample_ft_token_id2 = '112345678901234567890123456789012345678901234567890123456789000000000001' + balances[sample_ft_token_id1] = 22 + balances[sample_ft_token_id2] = 1 + sample_ft_token_id2_bytes = bytearray.fromhex(sample_ft_token_id2) + sample_ft_token_id2_bytes.reverse() + sample_ft_token_id2_encoded = encode_op_pushdata(sample_ft_token_id2_bytes) + state_hash = bytes.fromhex('0000000000000000000000000000000000000000000000000000000000000000') + reactor_context = ReactorContext(state_hash, dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps(balances), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) + payload = {} + request_tx_context = RequestTxContext(coin, mock_tx_hash, mock_tx, payload) + blockchain_context = RequestBlockchainContext(mock_headers, 840012) + script_context = ScriptContext(CScript(), CScript(bytes.fromhex('5100f7' + sample_ft_token_id2_encoded.hex() + '87'))) + updated_reactor_state = ConsensusVerifyScriptAvmExecute(script_context, blockchain_context, request_tx_context, reactor_context) + assert updated_reactor_state + +def test_atomicalsconsensus_OP_FT_ITEM_incoming_success1(): + balances = {} + sample_token_id1 = '012345678901234567890123456789012345678901234567890123456789000000000000' + sample_token_id1_bytes = bytearray.fromhex(sample_token_id1) + sample_token_id1_bytes.reverse() + sample_token_id1_encoded = encode_op_pushdata(sample_token_id1_bytes) + balances[sample_token_id1] = 1 + state_hash = bytes.fromhex('0000000000000000000000000000000000000000000000000000000000000000') + reactor_context = ReactorContext(state_hash, dumps({}), dumps({}), dumps({}), dumps({}), dumps(balances), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) + payload = {} + request_tx_context = RequestTxContext(coin, mock_tx_hash, mock_tx, payload) + blockchain_context = RequestBlockchainContext(mock_headers, 840012) + script_context = ScriptContext(CScript(), CScript(bytes.fromhex('0051f7' + sample_token_id1_encoded.hex() + '87'))) + updated_reactor_state = ConsensusVerifyScriptAvmExecute(script_context, blockchain_context, request_tx_context, reactor_context) + assert updated_reactor_state + +def test_atomicalsconsensus_OP_FT_ITEM_incoming_success2(): + balances = {} + sample_ft_token_id1 = '012345678901234567890123456789012345678901234567890123456789000000000000' + sample_ft_token_id2 = '112345678901234567890123456789012345678901234567890123456789000000000001' + balances[sample_ft_token_id1] = 22 + balances[sample_ft_token_id2] = 1 + sample_ft_token_id2_bytes = bytearray.fromhex(sample_ft_token_id2) + sample_ft_token_id2_bytes.reverse() + sample_ft_token_id2_encoded = encode_op_pushdata(sample_ft_token_id2_bytes) + state_hash = bytes.fromhex('0000000000000000000000000000000000000000000000000000000000000000') + reactor_context = ReactorContext(state_hash, dumps({}), dumps({}), dumps({}), dumps({}), dumps(balances), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) + payload = {} + request_tx_context = RequestTxContext(coin, mock_tx_hash, mock_tx, payload) + blockchain_context = RequestBlockchainContext(mock_headers, 840012) + script_context = ScriptContext(CScript(), CScript(bytes.fromhex('5151f7' + sample_ft_token_id2_encoded.hex() + '87'))) + updated_reactor_state = ConsensusVerifyScriptAvmExecute(script_context, blockchain_context, request_tx_context, reactor_context) + assert updated_reactor_state diff --git a/tests/lib/test_atomicalsconsensus_OP_FT_WITHDRAW.py b/tests/lib/test_atomicalsconsensus_OP_FT_WITHDRAW.py new file mode 100644 index 00000000..24d611dd --- /dev/null +++ b/tests/lib/test_atomicalsconsensus_OP_FT_WITHDRAW.py @@ -0,0 +1,374 @@ +import pytest + +from electrumx.lib.coins import Bitcoin +from electrumx.lib.hash import hex_str_to_hash + +from electrumx.lib.avm.util import ( + encode_int_value, + RequestInterpretParams, + RequestBlockchainContext, + print_result_states, + encode_op_pushdata, + RequestTxContext, + ReactorContext, + ScriptContext +) +from electrumx.lib.avm.avm import ( + AVMFactory +) +from electrumx.lib.util_atomicals import ( + location_id_bytes_to_compact +) + +import bitcointx +import re +from electrumx.lib.util import ( + pack_le_uint64, + pack_le_uint32 +) +import struct + +from bitcointx.core.atomicalsconsensus import ( + ConsensusVerifyScriptAvmExecute, + AtomicalConsensusExecutionError +) + +from bitcointx.core.script import ( + CScript, CScriptOp, OP_1NEGATE +) +from bitcointx.core._bignum import ( + bn2vch +) + +from cbor2 import dumps, loads, CBORDecodeError + +coin = Bitcoin + +class MockLogger: + def debug(self, msg): + return + def info(self, msg): + return + def warning(self, msg): + return + +mock_current_header = '000000209174c9f2757e2647733c9ab69c133b90257f85ce7e9c0100000000000000000096e490f16d161416bef825bd4716b0914f344be5835cf6a8f0de89288a6c440391af0566d3620317aca8414a' +mock_headers = { + '840012': mock_current_header +} +mock_blockchain_context = RequestBlockchainContext(mock_headers, 840012) +mock_rawtx = bytes.fromhex('02000000018e469f953413e8d865fcf1f47d759772aa05e8d78b1e4577a58edc8bd09344ff010000006b483045022100ce16646785907c919a1658496a85cf3f3d877d98ffca5eabd189524acc1de53b02205ac24a9e2855db3da26b840c82fddaf73a5a6eff4b5b78cadfb00584ad6bd5f3012102cbcad7b21fb5fb08ad55eb09e327b97f63e8c5e99b2faf9bb330545a5bd4602cfeffffff0203761700000000001976a91496d02c013f734a642871261324c58091a806c23188ac9ce52300000000001976a914a9a8d5aa3ec73688d0540d45be3842f4603705c588ac6e640800') +mock_tx, mock_tx_hash = coin.DESERIALIZER(mock_rawtx, 0).read_tx_and_hash() +mock_empty_reactor_context = ReactorContext(None, dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) + +def test_atomicalsconsensus_OP_FT_WITHDRAW_invalid_amount_for_no_id(): + with pytest.raises(AtomicalConsensusExecutionError) as exc: + payload = {} + balances = {} + sample_token_id1 = '012345678901234567890123456789012345678901234567890123456789000000000000' + sample_token_id1_bytes = bytearray.fromhex(sample_token_id1) + sample_token_id1_bytes.reverse() + sample_token_id1_encoded = encode_op_pushdata(sample_token_id1_bytes) + sample_token_id2 = '712345678901234567890123456789012345678901234567890123456789000000000002' + sample_token_id2_bytes = bytearray.fromhex(sample_token_id2) + sample_token_id2_bytes.reverse() + sample_token_id2_encoded = encode_op_pushdata(sample_token_id2_bytes) + balances[sample_token_id1] = 1 + state_hash = bytes.fromhex('0000000000000000000000000000000000000000000000000000000000000000') + request_tx_context = RequestTxContext(coin, mock_tx_hash, mock_tx, payload) + reactor_context = ReactorContext(state_hash, dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps(balances), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) + blockchain_context = RequestBlockchainContext(mock_headers, 840012) + script_context = ScriptContext(CScript(), CScript(bytes.fromhex(sample_token_id2_encoded.hex() + '0051f2'))) + updated_reactor_state = ConsensusVerifyScriptAvmExecute(script_context, blockchain_context, request_tx_context, reactor_context) + assert updated_reactor_state + assert exc.value.error_code == 0 + assert exc.value.script_error == 63 + assert exc.value.script_error_op_num == 3 + +def test_atomicalsconsensus_OP_FT_WITHDRAW_invalid_output_index(): + with pytest.raises(AtomicalConsensusExecutionError) as exc: + payload = {} + balances = {} + sample_token_id1 = '012345678901234567890123456789012345678901234567890123456789000000000000' + sample_token_id1_bytes = bytearray.fromhex(sample_token_id1) + sample_token_id1_bytes.reverse() + sample_token_id1_encoded = encode_op_pushdata(sample_token_id1_bytes) + sample_token_id2 = '712345678901234567890123456789012345678901234567890123456789000000000002' + sample_token_id2_bytes = bytearray.fromhex(sample_token_id2) + sample_token_id2_bytes.reverse() + sample_token_id2_encoded = encode_op_pushdata(sample_token_id2_bytes) + balances[sample_token_id1] = 1 + state_hash = bytes.fromhex('0000000000000000000000000000000000000000000000000000000000000000') + request_tx_context = RequestTxContext(coin, mock_tx_hash, mock_tx, payload) + reactor_context = ReactorContext(state_hash, dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps(balances), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) + blockchain_context = RequestBlockchainContext(mock_headers, 840012) + script_context = ScriptContext(CScript(), CScript(bytes.fromhex(sample_token_id2_encoded.hex() + '5851f2'))) + updated_reactor_state = ConsensusVerifyScriptAvmExecute(script_context, blockchain_context, request_tx_context, reactor_context) + assert updated_reactor_state + assert exc.value.error_code == 0 + assert exc.value.script_error == 64 + assert exc.value.script_error_op_num == 3 + +def test_atomicalsconsensus_OP_FT_WITHDRAW_invalid_amount_for_amount_0(): + with pytest.raises(AtomicalConsensusExecutionError) as exc: + payload = {} + balances = {} + sample_token_id1 = '012345678901234567890123456789012345678901234567890123456789000000000000' + sample_token_id1_bytes = bytearray.fromhex(sample_token_id1) + sample_token_id1_bytes.reverse() + sample_token_id1_encoded = encode_op_pushdata(sample_token_id1_bytes) + sample_token_id2 = '712345678901234567890123456789012345678901234567890123456789000000000002' + sample_token_id2_bytes = bytearray.fromhex(sample_token_id2) + sample_token_id2_bytes.reverse() + sample_token_id2_encoded = encode_op_pushdata(sample_token_id2_bytes) + balances[sample_token_id1] = 1 + too_big_amount = encode_int_value(999999999) + state_hash = bytes.fromhex('0000000000000000000000000000000000000000000000000000000000000000') + request_tx_context = RequestTxContext(coin, mock_tx_hash, mock_tx, payload) + reactor_context = ReactorContext(state_hash, dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps(balances), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) + blockchain_context = RequestBlockchainContext(mock_headers, 840012) + script_context = ScriptContext(CScript(), CScript(bytes.fromhex(sample_token_id1_encoded.hex() + '00' + too_big_amount.hex() + 'f2'))) + updated_reactor_state = ConsensusVerifyScriptAvmExecute(script_context, blockchain_context, request_tx_context, reactor_context) + assert updated_reactor_state + assert exc.value.error_code == 0 + assert exc.value.script_error == 62 + assert exc.value.script_error_op_num == 3 + +def test_atomicalsconsensus_OP_FT_WITHDRAW_no_balance_available_single_output(): + with pytest.raises(AtomicalConsensusExecutionError) as exc: + payload = {} + balances = {} + sample_token_id1 = '012345678901234567890123456789012345678901234567890123456789000000000000' + sample_token_id1_bytes = bytearray.fromhex(sample_token_id1) + sample_token_id1_bytes.reverse() + sample_token_id1_encoded = encode_op_pushdata(sample_token_id1_bytes) + sample_token_id2 = '712345678901234567890123456789012345678901234567890123456789000000000002' + sample_token_id2_bytes = bytearray.fromhex(sample_token_id2) + sample_token_id2_bytes.reverse() + sample_token_id2_encoded = encode_op_pushdata(sample_token_id2_bytes) + balances[sample_token_id1] = 2 + balances[sample_token_id2] = 3 + state_hash = bytes.fromhex('0000000000000000000000000000000000000000000000000000000000000000') + request_tx_context = RequestTxContext(coin, mock_tx_hash, mock_tx, payload) + reactor_context = ReactorContext(state_hash, dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps(balances), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) + blockchain_context = RequestBlockchainContext(mock_headers, 840012) + script_context = ScriptContext(CScript(), CScript(bytes.fromhex(sample_token_id1_encoded.hex() + '00' + '53' + 'f2'))) + updated_reactor_state = ConsensusVerifyScriptAvmExecute(script_context, blockchain_context, request_tx_context, reactor_context) + assert updated_reactor_state + assert exc.value.error_code == 0 + assert exc.value.script_error == 63 + assert exc.value.script_error_op_num == 3 + +def test_atomicalsconsensus_OP_FT_WITHDRAW_no_balance_available_multiple_outputs(): + with pytest.raises(AtomicalConsensusExecutionError) as exc: + payload = {} + balances = {} + sample_token_id1 = '012345678901234567890123456789012345678901234567890123456789000000000000' + sample_token_id1_bytes = bytearray.fromhex(sample_token_id1) + sample_token_id1_bytes.reverse() + sample_token_id1_encoded = encode_op_pushdata(sample_token_id1_bytes) + sample_token_id2 = '712345678901234567890123456789012345678901234567890123456789000000000002' + sample_token_id2_bytes = bytearray.fromhex(sample_token_id2) + sample_token_id2_bytes.reverse() + sample_token_id2_encoded = encode_op_pushdata(sample_token_id2_bytes) + balances[sample_token_id1] = 2 + balances[sample_token_id2] = 3 + state_hash = bytes.fromhex('0000000000000000000000000000000000000000000000000000000000000000') + request_tx_context = RequestTxContext(coin, mock_tx_hash, mock_tx, payload) + reactor_context = ReactorContext(state_hash, dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps(balances), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) + blockchain_context = RequestBlockchainContext(mock_headers, 840012) + script_context = ScriptContext(CScript(), CScript(bytes.fromhex(sample_token_id1_encoded.hex() + '00' + '51' + 'f2' + sample_token_id1_encoded.hex() + '51' + '52' + 'f2'))) + updated_reactor_state = ConsensusVerifyScriptAvmExecute(script_context, blockchain_context, request_tx_context, reactor_context) + assert updated_reactor_state + assert exc.value.error_code == 0 + assert exc.value.script_error == 63 + assert exc.value.script_error_op_num == 7 + +def test_atomicalsconsensus_OP_FT_WITHDRAW_adequate_balance_available_single_output(): + payload = {} + balances = {} + sample_token_id1 = '012345678901234567890123456789012345678901234567890123456789000000000000' + sample_token_id1_bytes = bytearray.fromhex(sample_token_id1) + sample_token_id1_bytes.reverse() + sample_token_id1_encoded = encode_op_pushdata(sample_token_id1_bytes) + sample_token_id2 = '712345678901234567890123456789012345678901234567890123456789000000000002' + sample_token_id2_bytes = bytearray.fromhex(sample_token_id2) + sample_token_id2_bytes.reverse() + sample_token_id2_encoded = encode_op_pushdata(sample_token_id2_bytes) + balances[sample_token_id1] = 2 + balances[sample_token_id2] = 3 + state_hash = bytes.fromhex('0000000000000000000000000000000000000000000000000000000000000000') + request_tx_context = RequestTxContext(coin, mock_tx_hash, mock_tx, payload) + reactor_context = ReactorContext(state_hash, dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps(balances), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) + blockchain_context = RequestBlockchainContext(mock_headers, 840012) + script_context = ScriptContext(CScript(), CScript(bytes.fromhex(sample_token_id1_encoded.hex() + '00' + '52' + 'f251'))) + updated_reactor_state = ConsensusVerifyScriptAvmExecute(script_context, blockchain_context, request_tx_context, reactor_context) + assert updated_reactor_state + expected_balances = {} + # expected_balances[sample_token_id1] = 0 + expected_balances[sample_token_id2] = 3 + assert updated_reactor_state.ft_balances == dumps(expected_balances) + print(loads(updated_reactor_state.state)) + assert len(loads(updated_reactor_state.state)) == 0 + assert len(loads(updated_reactor_state.state_updates)) == 0 + assert len(loads(updated_reactor_state.state_deletes)) == 0 + assert len(loads(updated_reactor_state.ft_incoming)) == 0 + assert len(loads(updated_reactor_state.nft_incoming)) == 0 + assert len(loads(updated_reactor_state.ft_balances)) == 1 + expected_balances_updates = {} + expected_balances_updates[sample_token_id1] = 0 + assert updated_reactor_state.ft_balances_updates == dumps(expected_balances_updates) + assert len(loads(updated_reactor_state.ft_balances_updates)) == 1 + assert len(loads(updated_reactor_state.nft_balances)) == 0 + assert len(loads(updated_reactor_state.nft_balances_updates)) == 0 + expected_ft_withdraws = {} + expected_ft_withdraws[sample_token_id1] = { + '0': 2 + } + print(loads(updated_reactor_state.ft_withdraws)) + assert updated_reactor_state.ft_withdraws == dumps(expected_ft_withdraws) + assert len(loads(updated_reactor_state.ft_withdraws)) == 1 + assert len(loads(updated_reactor_state.nft_withdraws)) == 0 + +def test_atomicalsconsensus_OP_FT_WITHDRAW_incoming_adequate_balance_multiple_output(): + payload = {} + balances = {} + sample_token_id1 = '012345678901234567890123456789012345678901234567890123456789000000000000' + sample_token_id1_bytes = bytearray.fromhex(sample_token_id1) + sample_token_id1_bytes.reverse() + sample_token_id1_encoded = encode_op_pushdata(sample_token_id1_bytes) + sample_token_id2 = '712345678901234567890123456789012345678901234567890123456789000000000002' + sample_token_id2_bytes = bytearray.fromhex(sample_token_id2) + sample_token_id2_bytes.reverse() + sample_token_id2_encoded = encode_op_pushdata(sample_token_id2_bytes) + balances[sample_token_id1] = 2 + balances[sample_token_id2] = 3 + state_hash = bytes.fromhex('0000000000000000000000000000000000000000000000000000000000000000') + request_tx_context = RequestTxContext(coin, mock_tx_hash, mock_tx, payload) + reactor_context = ReactorContext(state_hash, dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps(balances), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) + blockchain_context = RequestBlockchainContext(mock_headers, 840012) + script_context = ScriptContext(CScript(), CScript(bytes.fromhex(sample_token_id1_encoded.hex() + '00' + '51' + 'f2' + sample_token_id1_encoded.hex() + '51' + '51' + 'f2' + '51'))) + updated_reactor_state = ConsensusVerifyScriptAvmExecute(script_context, blockchain_context, request_tx_context, reactor_context) + assert updated_reactor_state + expected_balances = {} + # expected_balances[sample_token_id1] = 0 + expected_balances[sample_token_id2] = 3 + assert updated_reactor_state.ft_balances == dumps(expected_balances) + print(loads(updated_reactor_state.state)) + assert len(loads(updated_reactor_state.state)) == 0 + assert len(loads(updated_reactor_state.state_updates)) == 0 + assert len(loads(updated_reactor_state.state_deletes)) == 0 + assert len(loads(updated_reactor_state.ft_incoming)) == 0 + assert len(loads(updated_reactor_state.nft_incoming)) == 0 + assert len(loads(updated_reactor_state.ft_balances)) == 1 + expected_balances_updates = {} + expected_balances_updates[sample_token_id1] = 0 + assert updated_reactor_state.ft_balances_updates == dumps(expected_balances_updates) + assert len(loads(updated_reactor_state.ft_balances_updates)) == 1 + assert len(loads(updated_reactor_state.nft_balances)) == 0 + assert len(loads(updated_reactor_state.nft_balances_updates)) == 0 + expected_ft_withdraws = {} + expected_ft_withdraws[sample_token_id1] = { + '0': 1, + '1': 1 + } + print(loads(updated_reactor_state.ft_withdraws)) + assert updated_reactor_state.ft_withdraws == dumps(expected_ft_withdraws) + assert len(loads(updated_reactor_state.ft_withdraws)) == 1 + assert len(loads(updated_reactor_state.nft_withdraws)) == 0 + + +def test_atomicalsconsensus_OP_FT_WITHDRAW_incoming_added_balance_multiple_output(): + payload = {} + balances = {} + balances_incoming = {} + sample_token_id1 = '012345678901234567890123456789012345678901234567890123456789000000000000' + sample_token_id1_bytes = bytearray.fromhex(sample_token_id1) + sample_token_id1_bytes.reverse() + sample_token_id1_encoded = encode_op_pushdata(sample_token_id1_bytes) + sample_token_id2 = '712345678901234567890123456789012345678901234567890123456789000000000002' + sample_token_id2_bytes = bytearray.fromhex(sample_token_id2) + sample_token_id2_bytes.reverse() + sample_token_id2_encoded = encode_op_pushdata(sample_token_id2_bytes) + balances[sample_token_id1] = 2 + balances[sample_token_id2] = 3 + balances_incoming[sample_token_id1] = 3 + state_hash = bytes.fromhex('0000000000000000000000000000000000000000000000000000000000000000') + request_tx_context = RequestTxContext(coin, mock_tx_hash, mock_tx, payload) + reactor_context = ReactorContext(state_hash, dumps({}), dumps({}), dumps({}), dumps({}), dumps(balances_incoming), dumps({}), dumps({}), dumps(balances), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) + blockchain_context = RequestBlockchainContext(mock_headers, 840012) + script_context = ScriptContext(CScript(), CScript(bytes.fromhex(sample_token_id1_encoded.hex() + 'd3' + sample_token_id1_encoded.hex() + '00' + '53' + 'f2' + sample_token_id1_encoded.hex() + '51' + '52' + 'f2' + '51'))) + updated_reactor_state = ConsensusVerifyScriptAvmExecute(script_context, blockchain_context, request_tx_context, reactor_context) + expected_balances = {} + expected_balances[sample_token_id2] = 3 + assert updated_reactor_state.ft_balances == dumps(expected_balances) + assert len(loads(updated_reactor_state.state)) == 0 + assert len(loads(updated_reactor_state.state_updates)) == 0 + assert len(loads(updated_reactor_state.state_deletes)) == 0 + assert len(loads(updated_reactor_state.ft_incoming)) == 1 + assert len(loads(updated_reactor_state.nft_incoming)) == 0 + assert len(loads(updated_reactor_state.ft_balances)) == 1 + expected_balances_updates = {} + expected_balances_updates[sample_token_id1] = 0 + assert updated_reactor_state.ft_balances_updates == dumps(expected_balances_updates) + assert len(loads(updated_reactor_state.ft_balances_updates)) == 1 + assert len(loads(updated_reactor_state.nft_balances)) == 0 + assert len(loads(updated_reactor_state.nft_balances_updates)) == 0 + expected_ft_withdraws = {} + expected_ft_withdraws[sample_token_id1] = { + '0': 3, + '1': 2 + } + assert updated_reactor_state.ft_withdraws == dumps(expected_ft_withdraws) + assert len(loads(updated_reactor_state.ft_withdraws)) == 1 + assert len(loads(updated_reactor_state.nft_withdraws)) == 0 + +def test_atomicalsconsensus_OP_FT_WITHDRAW_incoming_added_multiple_balance_multiple_output(): + payload = {} + balances = {} + balances_incoming = {} + sample_token_id1 = '012345678901234567890123456789012345678901234567890123456789000000000000' + sample_token_id1_bytes = bytearray.fromhex(sample_token_id1) + sample_token_id1_bytes.reverse() + sample_token_id1_encoded = encode_op_pushdata(sample_token_id1_bytes) + sample_token_id2 = '712345678901234567890123456789012345678901234567890123456789000000000002' + sample_token_id2_bytes = bytearray.fromhex(sample_token_id2) + sample_token_id2_bytes.reverse() + sample_token_id2_encoded = encode_op_pushdata(sample_token_id2_bytes) + balances[sample_token_id1] = 2 + balances[sample_token_id2] = 3 + balances_incoming[sample_token_id1] = 3 + balances_incoming[sample_token_id2] = 4 + state_hash = bytes.fromhex('0000000000000000000000000000000000000000000000000000000000000000') + request_tx_context = RequestTxContext(coin, mock_tx_hash, mock_tx, payload) + reactor_context = ReactorContext(state_hash, dumps({}), dumps({}), dumps({}), dumps({}), dumps(balances_incoming), dumps({}), dumps({}), dumps(balances), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) + blockchain_context = RequestBlockchainContext(mock_headers, 840012) + script_context = ScriptContext(CScript(), CScript(bytes.fromhex(sample_token_id1_encoded.hex() + 'd3' + sample_token_id1_encoded.hex() + '00' + '53' + 'f2' + sample_token_id1_encoded.hex() + '51' + '52' + 'f2' + sample_token_id2_encoded.hex() + 'd3' + sample_token_id2_encoded.hex() + '51' + '56' + 'f2' + '51'))) + updated_reactor_state = ConsensusVerifyScriptAvmExecute(script_context, blockchain_context, request_tx_context, reactor_context) + expected_balances = {} + expected_balances[sample_token_id2] = 1 + assert updated_reactor_state.ft_balances == dumps(expected_balances) + assert len(loads(updated_reactor_state.state)) == 0 + assert len(loads(updated_reactor_state.state_updates)) == 0 + assert len(loads(updated_reactor_state.state_deletes)) == 0 + assert len(loads(updated_reactor_state.ft_incoming)) == 2 + assert len(loads(updated_reactor_state.nft_incoming)) == 0 + assert len(loads(updated_reactor_state.ft_balances)) == 1 + expected_balances_updates = {} + expected_balances_updates[sample_token_id1] = 0 + expected_balances_updates[sample_token_id2] = 1 + assert updated_reactor_state.ft_balances_updates == dumps(expected_balances_updates) + assert len(loads(updated_reactor_state.ft_balances_updates)) == 2 + assert len(loads(updated_reactor_state.nft_balances)) == 0 + assert len(loads(updated_reactor_state.nft_balances_updates)) == 0 + expected_ft_withdraws = {} + expected_ft_withdraws[sample_token_id1] = { + '0': 3, + '1': 2 + } + expected_ft_withdraws[sample_token_id2] = { + '1': 6 + } + assert updated_reactor_state.ft_withdraws == dumps(expected_ft_withdraws) + assert len(loads(updated_reactor_state.ft_withdraws)) == 2 + assert len(loads(updated_reactor_state.nft_withdraws)) == 0 diff --git a/tests/lib/test_atomicalsconsensus_OP_GETBLOCKINFO.py b/tests/lib/test_atomicalsconsensus_OP_GETBLOCKINFO.py new file mode 100644 index 00000000..ec42f44c --- /dev/null +++ b/tests/lib/test_atomicalsconsensus_OP_GETBLOCKINFO.py @@ -0,0 +1,232 @@ +import pytest + +from cbor2 import dumps +from electrumx.lib.coins import Bitcoin +from electrumx.lib.hash import hex_str_to_hash +from electrumx.lib.avm.avm import ( + AVMFactory, + RequestBlockchainContext, + RequestTxContext, + ReactorContext +) +from electrumx.lib.avm.util import ( + encode_int_value, + RequestBlockchainContext, +) +from bitcointx.core.atomicalsconsensus import ( + AtomicalConsensusExecutionError +) + +coin = Bitcoin +class MockLogger: + def debug(self, msg): + return + def info(self, msg): + return + def warning(self, msg): + return + +def mock_mint_fetcher(atomical_id): + return {} + +mock_current_header = '000000209174c9f2757e2647733c9ab69c133b90257f85ce7e9c0100000000000000000096e490f16d161416bef825bd4716b0914f344be5835cf6a8f0de89288a6c440391af0566d3620317aca8414a' +mock_headers = { + '840012': mock_current_header +} +mock_blockchain_context = RequestBlockchainContext(mock_headers, 840012) +mock_rawtx = bytes.fromhex('02000000018e469f953413e8d865fcf1f47d759772aa05e8d78b1e4577a58edc8bd09344ff010000006b483045022100ce16646785907c919a1658496a85cf3f3d877d98ffca5eabd189524acc1de53b02205ac24a9e2855db3da26b840c82fddaf73a5a6eff4b5b78cadfb00584ad6bd5f3012102cbcad7b21fb5fb08ad55eb09e327b97f63e8c5e99b2faf9bb330545a5bd4602cfeffffff0203761700000000001976a91496d02c013f734a642871261324c58091a806c23188ac9ce52300000000001976a914a9a8d5aa3ec73688d0540d45be3842f4603705c588ac6e640800') +mock_tx, mock_tx_hash = coin.DESERIALIZER(mock_rawtx, 0).read_tx_and_hash() +mock_empty_reactor_context = ReactorContext(None, dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) + +def test_atomicalsconsensus_OP_GETBLOCKINFO_fail_missing_fields(): + protocol_mint_data = { + 'p': 'ppp', + 'code': bytes.fromhex('0059fb5187'), + 'fn': [ + { + 'name': 'ctor', + 'params': [ + ] + } + ] + } + deploy_payload = { + 'p': 'ppp', + 'u': b'', + 'args': { + } + } + + avm = AVMFactory(MockLogger(), mock_mint_fetcher, mock_blockchain_context, protocol_mint_data) + atomicals_spent_at_inputs = {} + request_tx_context = RequestTxContext(coin, mock_tx_hash, mock_tx, deploy_payload) + deploy_command = avm.create_deploy_command(request_tx_context, atomicals_spent_at_inputs, mock_empty_reactor_context) + assert(deploy_command.is_valid) + assert(deploy_command.unlock_script.hex() == '') + assert(deploy_command.lock_script.hex() == '0059fb5187') + + result = deploy_command.execute() + assert not result.success + assert not result.reactor_context + assert result.error.error_code == 0 + assert result.error.script_error == 76 + assert result.error.script_error_op_num == 2 + +def test_atomicalsconsensus_OP_GETBLOCKINFO_current_00_success(): + encoded_height = encode_int_value(840012).hex() + lock_script = '0058fb' + encoded_height + '87' + protocol_mint_data = { + 'p': 'ppp', + 'code': bytes.fromhex(lock_script), + 'fn': [ + { + 'name': 'ctor', + 'params': [ + ] + } + ] + } + deploy_payload = { + 'op': 'deploy', + 'p': 'ppp', + 'args': { + } + } + + avm = AVMFactory(MockLogger(), mock_mint_fetcher, mock_blockchain_context, protocol_mint_data) + atomicals_spent_at_inputs = {} + request_tx_context = RequestTxContext(coin, mock_tx_hash, mock_tx, deploy_payload) + deploy_command = avm.create_deploy_command(request_tx_context, atomicals_spent_at_inputs, mock_empty_reactor_context) + assert(deploy_command.is_valid) + assert(deploy_command.unlock_script.hex() == '') + assert(deploy_command.lock_script.hex() == lock_script) + result = deploy_command.execute() + assert result.success + +def test_atomicalsconsensus_OP_GETBLOCKINFO_deploy_success1(): + block_header = '000000209174c9f2757e2647733c9ab69c133b90257f85ce7e9c0100000000000000000096e490f16d161416bef825bd4716b0914f344be5835cf6a8f0de89288a6c440391af0566d3620317aca8414a' + lock_script = 'fb' + '4c50' + block_header + '87' + protocol_mint_data = { + 'p': 'ppp', + 'code': bytes.fromhex(lock_script), + 'fn': [ + { + 'name': 'ctor', + 'params': [ + { + 'name': 'height', + 'type': 'int' + }, + { + 'name': 'field', + 'type': 'int' + } + ] + } + ] + } + deploy_payload = { + 'p': 'ppp', + 'u': bytes.fromhex('0057'), + 'args': { + } + } + avm = AVMFactory(MockLogger(), mock_mint_fetcher, mock_blockchain_context, protocol_mint_data) + atomicals_spent_at_inputs = {} + request_tx_context = RequestTxContext(coin, mock_tx_hash, mock_tx, deploy_payload) + deploy_command = avm.create_deploy_command(request_tx_context, atomicals_spent_at_inputs, mock_empty_reactor_context) + assert(deploy_command.is_valid) + assert(deploy_command.unlock_script.hex() == '0057') + assert(deploy_command.lock_script.hex() == lock_script) + result = deploy_command.execute() + assert result.success + +def test_atomicalsconsensus_OP_GETBLOCKINFO_deploy_success_items(): + block_header = '000000209174c9f2757e2647733c9ab69c133b90257f85ce7e9c0100000000000000000096e490f16d161416bef825bd4716b0914f344be5835cf6a8f0de89288a6c440391af0566d3620317aca8414a' + block_items = [ + { + 'field': 0, + 'field_enc': '00', + 'result': '0400000020' + }, + { + 'field': 1, + 'field_enc': '51', + 'result': '20' + '9174c9f2757e2647733c9ab69c133b90257f85ce7e9c01000000000000000000' + }, + { + 'field': 2, + 'field_enc': '52', + 'result': '20' + '96e490f16d161416bef825bd4716b0914f344be5835cf6a8f0de89288a6c4403' + }, + { + 'field': 3, + 'field_enc': '53', + 'result': encode_int_value(1711648657).hex() + }, + { + 'field': 4, + 'field_enc': '54', + 'result': encode_int_value(386097875).hex(), + }, + { + 'field': 5, + 'field_enc': '55', + 'result': encode_int_value(1245817004).hex() + }, + { + 'field': 6, + 'field_enc': '56', + 'result': encode_int_value(83126997340025).hex() + }, + { + 'field': 7, + 'field_enc': '57', + 'result': '4c50' + block_header + }, + { + 'field': 8, + 'field_enc': '58', + 'result': encode_int_value(840012).hex() + } + ] + + i = 0 + + for item in block_items: + lock_script = 'fb' + item['result'] + '87' + protocol_mint_data = { + 'p': 'ppp', + 'code': bytes.fromhex(lock_script), + 'fn': [ + { + 'name': 'ctor', + 'params': [ + { + 'name': 'height', + 'type': 'int' + }, + { + 'name': 'field', + 'type': 'int' + } + ] + } + ] + } + deploy_payload = { + 'p': 'ppp', + 'u': bytes.fromhex('00' + item['field_enc']), + 'args': { + } + } + avm = AVMFactory(MockLogger(), mock_mint_fetcher, mock_blockchain_context, protocol_mint_data) + atomicals_spent_at_inputs = {} + request_tx_context = RequestTxContext(coin, mock_tx_hash, mock_tx, deploy_payload) + deploy_command = avm.create_deploy_command(request_tx_context, atomicals_spent_at_inputs, mock_empty_reactor_context) + assert(deploy_command.is_valid) + assert(deploy_command.unlock_script.hex() == '00' + item['field_enc']) + assert(deploy_command.lock_script.hex() == lock_script) + result = deploy_command.execute() + assert result.success + \ No newline at end of file diff --git a/tests/lib/test_atomicalsconsensus_OP_HASH_FN.py b/tests/lib/test_atomicalsconsensus_OP_HASH_FN.py new file mode 100644 index 00000000..cbf4245d --- /dev/null +++ b/tests/lib/test_atomicalsconsensus_OP_HASH_FN.py @@ -0,0 +1,268 @@ +import pytest + +from electrumx.lib.coins import Bitcoin + +from electrumx.lib.avm.avm import ( + AVMFactory +) + +from electrumx.lib.avm.avm import ( + AVMFactory, + RequestBlockchainContext, + RequestTxContext, + ReactorContext +) + +from electrumx.lib.avm.util import ( + encode_int_value, + RequestBlockchainContext, +) + +from bitcointx.core.atomicalsconsensus import ( + AtomicalConsensusExecutionError +) + +from electrumx.lib.avm.util import ( + encode_int_value, + RequestBlockchainContext, + RequestInterpretParams, + encode_op_pushdata, + print_result_states +) +from electrumx.lib.avm.pow_funcs import ( + calc_sha3_256, + calc_sha512, + calc_sha512_256, + calc_eaglesong +) + +from bitcointx.core.atomicalsconsensus import ( + ConsensusVerifyScriptAvmExecute +) + +from bitcointx.core.script import ( + CScript +) +from bitcointx.core._bignum import ( + bn2vch +) + +from cbor2 import dumps, loads, CBORDecodeError + +mock_block_header = '000000209174c9f2757e2647733c9ab69c133b90257f85ce7e9c0100000000000000000096e490f16d161416bef825bd4716b0914f344be5835cf6a8f0de89288a6c440391af0566d3620317aca8414a' + +mock_contract_external_state_cbor = dumps({ + 'header': mock_block_header, + 'height': 820123 +}) + + +coin = Bitcoin + +class MockLogger: + def debug(self, msg): + return + def info(self, msg): + return + def warning(self, msg): + return + +def mock_mint_fetcher(atomical_id): + return {} + +mock_current_header = '000000209174c9f2757e2647733c9ab69c133b90257f85ce7e9c0100000000000000000096e490f16d161416bef825bd4716b0914f344be5835cf6a8f0de89288a6c440391af0566d3620317aca8414a' +mock_headers = { + '840012': mock_current_header +} +mock_blockchain_context = RequestBlockchainContext(mock_headers, 840012) +mock_rawtx = bytes.fromhex('02000000018e469f953413e8d865fcf1f47d759772aa05e8d78b1e4577a58edc8bd09344ff010000006b483045022100ce16646785907c919a1658496a85cf3f3d877d98ffca5eabd189524acc1de53b02205ac24a9e2855db3da26b840c82fddaf73a5a6eff4b5b78cadfb00584ad6bd5f3012102cbcad7b21fb5fb08ad55eb09e327b97f63e8c5e99b2faf9bb330545a5bd4602cfeffffff0203761700000000001976a91496d02c013f734a642871261324c58091a806c23188ac9ce52300000000001976a914a9a8d5aa3ec73688d0540d45be3842f4603705c588ac6e640800') +mock_tx, mock_tx_hash = coin.DESERIALIZER(mock_rawtx, 0).read_tx_and_hash() +mock_empty_reactor_context = ReactorContext(None, dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) + +def test_atomicalsconsensus_OP_HASH_FN_SHA3_256_success1(): + sha3_256_preimage = '12345678901234567890' + sha3_256_preimage_bytes = bytes.fromhex(sha3_256_preimage) + sha3_256_hash_value = calc_sha3_256(sha3_256_preimage_bytes) + sha3_256_preimage_bytes_encoded = encode_op_pushdata(sha3_256_preimage_bytes) + to_hash_value_data = encode_op_pushdata(sha3_256_hash_value) + + assert('8f9e20c10b08813adeb3027e464f3bbe6f0f2220536a4ec2cd9b03444e18a8ad' == sha3_256_hash_value.hex()) + lock_script = sha3_256_preimage_bytes_encoded.hex() + '00fd' + to_hash_value_data.hex() + '87' + protocol_mint_data = { + 'p': 'ppp', + 'code': bytes.fromhex(lock_script), + 'fn': [ + { + 'name': 'ctor', + 'params': [ + ] + } + ] + } + deploy_payload = { + 'op': 'deploy', + 'p': 'ppp', + 'args': { + } + } + + avm = AVMFactory(MockLogger(), mock_mint_fetcher, mock_blockchain_context, protocol_mint_data) + atomicals_spent_at_inputs = {} + request_tx_context = RequestTxContext(coin, mock_tx_hash, mock_tx, deploy_payload) + deploy_command = avm.create_deploy_command(request_tx_context, atomicals_spent_at_inputs, mock_empty_reactor_context) + assert(deploy_command.is_valid) + assert(deploy_command.unlock_script.hex() == '') + assert(deploy_command.lock_script.hex() == lock_script) + result = deploy_command.execute() + assert result.success + +def test_atomicalsconsensus_OP_HASH_FN_SHA3_256_delete1(): + sha3_256_preimage = '9999' + sha3_256_preimage_bytes = bytes.fromhex(sha3_256_preimage) + sha3_256_hash_value = calc_sha3_256(sha3_256_preimage_bytes) + to_hash_value_data = encode_op_pushdata(sha3_256_hash_value) + different_bytes = encode_op_pushdata(b'123') + lock_script = different_bytes.hex() + '00fd' + to_hash_value_data.hex() + '87' + protocol_mint_data = { + 'p': 'ppp', + 'code': bytes.fromhex(lock_script), + 'fn': [ + { + 'name': 'ctor', + 'params': [ + ] + } + ] + } + deploy_payload = { + 'op': 'deploy', + 'p': 'ppp', + 'args': { + } + } + + avm = AVMFactory(MockLogger(), mock_mint_fetcher, mock_blockchain_context, protocol_mint_data) + atomicals_spent_at_inputs = {} + request_tx_context = RequestTxContext(coin, mock_tx_hash, mock_tx, deploy_payload) + deploy_command = avm.create_deploy_command(request_tx_context, atomicals_spent_at_inputs, mock_empty_reactor_context) + assert(deploy_command.is_valid) + assert(deploy_command.unlock_script.hex() == '') + assert(deploy_command.lock_script.hex() == lock_script) + result = deploy_command.execute() + assert not result.success + assert not result.reactor_context + assert result.error.error_code == 0 + assert result.error.script_error ==2 + assert result.error.script_error_op_num == 4 + + + +def test_atomicalsconsensus_OP_HASH_FN_SHA512_success1(): + preimage = '12345678901234567890' + preimage_bytes = bytes.fromhex(preimage) + hash_value = calc_sha512(preimage_bytes) + preimage_bytes_encoded = encode_op_pushdata(preimage_bytes) + to_hash_value_data = encode_op_pushdata(hash_value) + assert('cda7e420d1669a40d5511d4ba48d5d9b2df052ad3f81af429fdf77b4786f9507f3ff95b2206c287accb43f6bb3a98a821dbffee4947a09b77cb90b4d2874df42' == hash_value.hex()) + lock_script = preimage_bytes_encoded.hex() + '51fd' + to_hash_value_data.hex() + '87' + protocol_mint_data = { + 'p': 'ppp', + 'code': bytes.fromhex(lock_script), + 'fn': [ + { + 'name': 'ctor', + 'params': [ + ] + } + ] + } + deploy_payload = { + 'op': 'deploy', + 'p': 'ppp', + 'args': { + } + } + + avm = AVMFactory(MockLogger(), mock_mint_fetcher, mock_blockchain_context, protocol_mint_data) + atomicals_spent_at_inputs = {} + request_tx_context = RequestTxContext(coin, mock_tx_hash, mock_tx, deploy_payload) + deploy_command = avm.create_deploy_command(request_tx_context, atomicals_spent_at_inputs, mock_empty_reactor_context) + assert(deploy_command.is_valid) + assert(deploy_command.unlock_script.hex() == '') + assert(deploy_command.lock_script.hex() == lock_script) + result = deploy_command.execute() + assert result.success + +def test_atomicalsconsensus_OP_HASH_FN_SHA512_256_success1(): + preimage = '12345678901234567890' + preimage_bytes = bytes.fromhex(preimage) + hash_value = calc_sha512_256(preimage_bytes) + preimage_bytes_encoded = encode_op_pushdata(preimage_bytes) + to_hash_value_data = encode_op_pushdata(hash_value) + + assert('f7c1a111dc74c4cafe5d6a0aa265c1f1592d3759fee2faaefe3080f5e6bb57f4' == hash_value.hex()) + lock_script = preimage_bytes_encoded.hex() + '52fd' + to_hash_value_data.hex() + '87' + protocol_mint_data = { + 'p': 'ppp', + 'code': bytes.fromhex(lock_script), + 'fn': [ + { + 'name': 'ctor', + 'params': [ + ] + } + ] + } + deploy_payload = { + 'op': 'deploy', + 'p': 'ppp', + 'args': { + } + } + + avm = AVMFactory(MockLogger(), mock_mint_fetcher, mock_blockchain_context, protocol_mint_data) + atomicals_spent_at_inputs = {} + request_tx_context = RequestTxContext(coin, mock_tx_hash, mock_tx, deploy_payload) + deploy_command = avm.create_deploy_command(request_tx_context, atomicals_spent_at_inputs, mock_empty_reactor_context) + assert(deploy_command.is_valid) + assert(deploy_command.unlock_script.hex() == '') + assert(deploy_command.lock_script.hex() == lock_script) + result = deploy_command.execute() + assert result.success + + +def test_atomicalsconsensus_OP_HASH_FN_eaglesong_success1(): + preimage = '12345678901234567890' + preimage_bytes = bytes.fromhex(preimage) + hash_value = calc_eaglesong(preimage_bytes) + preimage_bytes_encoded = encode_op_pushdata(preimage_bytes) + to_hash_value_data = encode_op_pushdata(hash_value) + assert('087e1c61707d800e15a904abe09ec03bb18829a33a919073180c3587bdfb3385' == hash_value.hex()) + lock_script = preimage_bytes_encoded.hex() + '53fd' + to_hash_value_data.hex() + '87' + protocol_mint_data = { + 'p': 'ppp', + 'code': bytes.fromhex(lock_script), + 'fn': [ + { + 'name': 'ctor', + 'params': [ + ] + } + ] + } + deploy_payload = { + 'op': 'deploy', + 'p': 'ppp', + 'args': { + } + } + + avm = AVMFactory(MockLogger(), mock_mint_fetcher, mock_blockchain_context, protocol_mint_data) + atomicals_spent_at_inputs = {} + request_tx_context = RequestTxContext(coin, mock_tx_hash, mock_tx, deploy_payload) + deploy_command = avm.create_deploy_command(request_tx_context, atomicals_spent_at_inputs, mock_empty_reactor_context) + assert(deploy_command.is_valid) + assert(deploy_command.unlock_script.hex() == '') + assert(deploy_command.lock_script.hex() == lock_script) + result = deploy_command.execute() + assert result.success \ No newline at end of file diff --git a/tests/lib/test_atomicalsconsensus_OP_INPUTBYTECODE.py b/tests/lib/test_atomicalsconsensus_OP_INPUTBYTECODE.py new file mode 100644 index 00000000..b9a6414c --- /dev/null +++ b/tests/lib/test_atomicalsconsensus_OP_INPUTBYTECODE.py @@ -0,0 +1,163 @@ +import pytest + +from electrumx.lib.coins import Bitcoin +from electrumx.lib.hash import hex_str_to_hash + +from electrumx.lib.avm.avm import ( + AVMFactory, + RequestBlockchainContext, + RequestTxContext, + ReactorContext +) + +from electrumx.lib.avm.util import ( + encode_int_value, + encode_data_value +) + +from cbor2 import dumps, loads +coin = Bitcoin + +class MockLogger: + def debug(self, msg): + return + def info(self, msg): + return + def warning(self, msg): + return + +def mock_mint_fetcher(atomical_id): + return { + } + +mock_current_header = '000000209174c9f2757e2647733c9ab69c133b90257f85ce7e9c0100000000000000000096e490f16d161416bef825bd4716b0914f344be5835cf6a8f0de89288a6c440391af0566d3620317aca8414a' +mock_headers = { + '840012': mock_current_header +} + +mock_blockchain_context = RequestBlockchainContext(mock_headers, 840012) +mock_empty_reactor_context = ReactorContext(None, dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) +mock_rawtx2 = bytes.fromhex('0100000000010156d0f907b0a3385095afb426f46762f4305f39c30b626753b0365c51307f6bac0000000000ffffffff01e803000000000000225120c2529c35bacd75646eb3135ca1270325ba2bab5d473b7c8a1eab54b21deea86e034090e7d4e121a12d1ec63820af4d59663a54c31ca4da6dd861257b728a69c8f61cfad799fce6fc6388eba63e02eca4a3c7df82fa055cd6c2e5c771749c71744a0474209f76f4e90e426cae195e7092cf8ec81d3005a90574e6d0532c679983ad79d7eaac00630461746f6d036e657745a46170667365636f6e64626f70666465706c6f796461726773a36474696d651a66845c51656e6f6e63650068626974776f726b636161646e616d6569636f6e7472616374316821c09f76f4e90e426cae195e7092cf8ec81d3005a90574e6d0532c679983ad79d7ea00000000') +mock_tx2, mock_tx_hash2 = coin.DESERIALIZER(mock_rawtx2, 0).read_tx_and_hash() + +def test_execute_deploy_script_OP_INPUTBYTECODE_empty(): + code_script = bytes.fromhex('00ca' + '00' + '87') + protocol_mint_data = { + 'p': 'ppp', + 'code': code_script, + 'fn': [ + { + 'name': 'ctor', + 'params': [ + ] + } + ] + } + avm = AVMFactory(MockLogger(), mock_mint_fetcher, mock_blockchain_context, protocol_mint_data) + payload = { + 'op': 'deploy', + 'p': 'ppp', + 'args': { + } + } + atomicals_spent_at_inputs = {} + request_tx_context = RequestTxContext(coin, mock_tx_hash2, mock_tx2, payload) + + deploy_command = avm.create_deploy_command(request_tx_context, atomicals_spent_at_inputs, mock_empty_reactor_context) + assert(deploy_command.is_valid) + assert(deploy_command.unlock_script.hex() == '') + assert(deploy_command.lock_script.hex() == code_script.hex()) + + result = deploy_command.execute() + assert result.success + assert result.reactor_context + + result_context = result.reactor_context + state_hash = result_context.state_hash + state = loads(result_context.state) + state_updates = loads(result_context.state_updates) + state_deletes = loads(result_context.state_deletes) + ft_incoming = loads(result_context.ft_incoming) + nft_incoming = loads(result_context.nft_incoming) + ft_balances = loads(result_context.ft_balances) + ft_balances_updates = loads(result_context.ft_balances_updates) + nft_balances = loads(result_context.nft_balances) + nft_balances_updates = loads(result_context.nft_balances_updates) + ft_withdraws = loads(result_context.ft_withdraws) + nft_withdraws = loads(result_context.nft_withdraws) + + assert state_hash.hex() == '71daaf262004b5778dfb085daf074cf22a9e4c6f60eb8700974ba6bd3cc2b156' + assert len(state) == 0 + assert len(state_updates) == 0 + assert len(state_deletes) == 0 + assert len(ft_incoming) == 0 + assert len(nft_incoming) == 0 + assert len(ft_balances) == 0 + assert len(ft_balances_updates) == 0 + assert len(nft_balances) == 0 + assert len(nft_balances_updates) == 0 + assert len(ft_withdraws) == 0 + assert len(nft_withdraws) == 0 + +mock_rawtx_not_taproot = bytes.fromhex('02000000018e469f953413e8d865fcf1f47d759772aa05e8d78b1e4577a58edc8bd09344ff010000006b483045022100ce16646785907c919a1658496a85cf3f3d877d98ffca5eabd189524acc1de53b02205ac24a9e2855db3da26b840c82fddaf73a5a6eff4b5b78cadfb00584ad6bd5f3012102cbcad7b21fb5fb08ad55eb09e327b97f63e8c5e99b2faf9bb330545a5bd4602cfeffffff0203761700000000001976a91496d02c013f734a642871261324c58091a806c23188ac9ce52300000000001976a914a9a8d5aa3ec73688d0540d45be3842f4603705c588ac6e640800') +mock_tx_not_taproot, mock_tx_hash_not_taproot = coin.DESERIALIZER(mock_rawtx_not_taproot, 0).read_tx_and_hash() + +def test_execute_deploy_script_OP_INPUTBYTECODE_not_empty(): + code_script = bytes.fromhex('00ca' + '4c6b' + '483045022100ce16646785907c919a1658496a85cf3f3d877d98ffca5eabd189524acc1de53b02205ac24a9e2855db3da26b840c82fddaf73a5a6eff4b5b78cadfb00584ad6bd5f3012102cbcad7b21fb5fb08ad55eb09e327b97f63e8c5e99b2faf9bb330545a5bd4602c' + '87') + protocol_mint_data = { + 'p': 'ppp', + 'code': code_script, + 'fn': [ + { + 'name': 'ctor', + 'params': [ + ] + } + ] + } + avm = AVMFactory(MockLogger(), mock_mint_fetcher, mock_blockchain_context, protocol_mint_data) + payload = { + 'op': 'deploy', + 'p': 'ppp', + 'args': { + } + } + atomicals_spent_at_inputs = {} + request_tx_context = RequestTxContext(coin, mock_tx_hash_not_taproot, mock_tx_not_taproot, payload) + + deploy_command = avm.create_deploy_command(request_tx_context, atomicals_spent_at_inputs, mock_empty_reactor_context) + assert(deploy_command.is_valid) + assert(deploy_command.unlock_script.hex() == '') + assert(deploy_command.lock_script.hex() == code_script.hex()) + + result = deploy_command.execute() + assert result.success + assert result.reactor_context + + result_context = result.reactor_context + state_hash = result_context.state_hash + state = loads(result_context.state) + state_updates = loads(result_context.state_updates) + state_deletes = loads(result_context.state_deletes) + ft_incoming = loads(result_context.ft_incoming) + nft_incoming = loads(result_context.nft_incoming) + ft_balances = loads(result_context.ft_balances) + ft_balances_updates = loads(result_context.ft_balances_updates) + nft_balances = loads(result_context.nft_balances) + nft_balances_updates = loads(result_context.nft_balances_updates) + ft_withdraws = loads(result_context.ft_withdraws) + nft_withdraws = loads(result_context.nft_withdraws) + + assert state_hash.hex() == '71daaf262004b5778dfb085daf074cf22a9e4c6f60eb8700974ba6bd3cc2b156' + assert len(state) == 0 + assert len(state_updates) == 0 + assert len(state_deletes) == 0 + assert len(ft_incoming) == 0 + assert len(nft_incoming) == 0 + assert len(ft_balances) == 0 + assert len(ft_balances_updates) == 0 + assert len(nft_balances) == 0 + assert len(nft_balances_updates) == 0 + assert len(ft_withdraws) == 0 + assert len(nft_withdraws) == 0 + diff --git a/tests/lib/test_atomicalsconsensus_OP_INPUTSEQUENCENUMBER.py b/tests/lib/test_atomicalsconsensus_OP_INPUTSEQUENCENUMBER.py new file mode 100644 index 00000000..4b6ccda3 --- /dev/null +++ b/tests/lib/test_atomicalsconsensus_OP_INPUTSEQUENCENUMBER.py @@ -0,0 +1,101 @@ +import pytest + +from electrumx.lib.coins import Bitcoin +from electrumx.lib.hash import hex_str_to_hash + +from electrumx.lib.avm.avm import ( + AVMFactory, + RequestBlockchainContext, + RequestTxContext, + ReactorContext +) + +from electrumx.lib.avm.util import ( + encode_int_value, +) + +from cbor2 import dumps, loads +coin = Bitcoin + +class MockLogger: + def debug(self, msg): + return + def info(self, msg): + return + def warning(self, msg): + return + +def mock_mint_fetcher(atomical_id): + return { + } + +mock_current_header = '000000209174c9f2757e2647733c9ab69c133b90257f85ce7e9c0100000000000000000096e490f16d161416bef825bd4716b0914f344be5835cf6a8f0de89288a6c440391af0566d3620317aca8414a' +mock_headers = { + '840012': mock_current_header +} +mock_blockchain_context = RequestBlockchainContext(mock_headers, 840012) +mock_empty_reactor_context = ReactorContext(None, dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) +mock_rawtx2 = bytes.fromhex('0100000000010156d0f907b0a3385095afb426f46762f4305f39c30b626753b0365c51307f6bac0000000000ffffffff01e803000000000000225120c2529c35bacd75646eb3135ca1270325ba2bab5d473b7c8a1eab54b21deea86e034090e7d4e121a12d1ec63820af4d59663a54c31ca4da6dd861257b728a69c8f61cfad799fce6fc6388eba63e02eca4a3c7df82fa055cd6c2e5c771749c71744a0474209f76f4e90e426cae195e7092cf8ec81d3005a90574e6d0532c679983ad79d7eaac00630461746f6d036e657745a46170667365636f6e64626f70666465706c6f796461726773a36474696d651a66845c51656e6f6e63650068626974776f726b636161646e616d6569636f6e7472616374316821c09f76f4e90e426cae195e7092cf8ec81d3005a90574e6d0532c679983ad79d7ea00000000') +mock_tx2, mock_tx_hash2 = coin.DESERIALIZER(mock_rawtx2, 0).read_tx_and_hash() + +def test_execute_deploy_script_OP_INPUTWITNESSBYTECODE(): + code_script = bytes.fromhex('00cb' + encode_int_value(4294967295, True) + '87') + protocol_mint_data = { + 'p': 'ppp', + 'code': code_script, + 'fn': [ + { + 'name': 'ctor', + 'params': [ + ] + } + ] + } + avm = AVMFactory(MockLogger(), mock_mint_fetcher, mock_blockchain_context, protocol_mint_data) + payload = { + 'op': 'deploy', + 'p': 'ppp', + 'args': { + } + } + atomicals_spent_at_inputs = {} + request_tx_context = RequestTxContext(coin, mock_tx_hash2, mock_tx2, payload) + + deploy_command = avm.create_deploy_command(request_tx_context, atomicals_spent_at_inputs, mock_empty_reactor_context) + assert(deploy_command.is_valid) + assert(deploy_command.unlock_script.hex() == '') + assert(deploy_command.lock_script.hex() == code_script.hex()) + + result = deploy_command.execute() + assert result.success + assert result.reactor_context + + result_context = result.reactor_context + state_hash = result_context.state_hash + state = loads(result_context.state) + state_updates = loads(result_context.state_updates) + state_deletes = loads(result_context.state_deletes) + ft_incoming = loads(result_context.ft_incoming) + nft_incoming = loads(result_context.nft_incoming) + ft_balances = loads(result_context.ft_balances) + ft_balances_updates = loads(result_context.ft_balances_updates) + nft_balances = loads(result_context.nft_balances) + nft_balances_updates = loads(result_context.nft_balances_updates) + ft_withdraws = loads(result_context.ft_withdraws) + nft_withdraws = loads(result_context.nft_withdraws) + + assert state_hash.hex() == '71daaf262004b5778dfb085daf074cf22a9e4c6f60eb8700974ba6bd3cc2b156' + assert len(state) == 0 + assert len(state_updates) == 0 + assert len(state_deletes) == 0 + assert len(ft_incoming) == 0 + assert len(nft_incoming) == 0 + assert len(ft_balances) == 0 + assert len(ft_balances_updates) == 0 + assert len(nft_balances) == 0 + assert len(nft_balances_updates) == 0 + assert len(ft_withdraws) == 0 + assert len(nft_withdraws) == 0 + + + diff --git a/tests/lib/test_atomicalsconsensus_OP_INPUTWITNESSBYTECODE.py b/tests/lib/test_atomicalsconsensus_OP_INPUTWITNESSBYTECODE.py new file mode 100644 index 00000000..b08b67ae --- /dev/null +++ b/tests/lib/test_atomicalsconsensus_OP_INPUTWITNESSBYTECODE.py @@ -0,0 +1,35 @@ +import pytest + +from electrumx.lib.coins import Bitcoin +from electrumx.lib.hash import hex_str_to_hash + +from electrumx.lib.avm.avm import ( + AVMFactory, + RequestBlockchainContext, + RequestTxContext, + ReactorContext +) +from cbor2 import dumps, loads +coin = Bitcoin + +class MockLogger: + def debug(self, msg): + return + def info(self, msg): + return + def warning(self, msg): + return + +def mock_mint_fetcher(atomical_id): + return { + } + +mock_current_header = '000000209174c9f2757e2647733c9ab69c133b90257f85ce7e9c0100000000000000000096e490f16d161416bef825bd4716b0914f344be5835cf6a8f0de89288a6c440391af0566d3620317aca8414a' +mock_headers = { + '840012': mock_current_header +} +mock_blockchain_context = RequestBlockchainContext(mock_headers, 840012) +mock_empty_reactor_context = ReactorContext(None, dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) +mock_rawtx2 = bytes.fromhex('0100000000010156d0f907b0a3385095afb426f46762f4305f39c30b626753b0365c51307f6bac0000000000ffffffff01e803000000000000225120c2529c35bacd75646eb3135ca1270325ba2bab5d473b7c8a1eab54b21deea86e034090e7d4e121a12d1ec63820af4d59663a54c31ca4da6dd861257b728a69c8f61cfad799fce6fc6388eba63e02eca4a3c7df82fa055cd6c2e5c771749c71744a0474209f76f4e90e426cae195e7092cf8ec81d3005a90574e6d0532c679983ad79d7eaac00630461746f6d036e657745a46170667365636f6e64626f70666465706c6f796461726773a36474696d651a66845c51656e6f6e63650068626974776f726b636161646e616d6569636f6e7472616374316821c09f76f4e90e426cae195e7092cf8ec81d3005a90574e6d0532c679983ad79d7ea00000000') +mock_tx2, mock_tx_hash2 = coin.DESERIALIZER(mock_rawtx2, 0).read_tx_and_hash() + \ No newline at end of file diff --git a/tests/lib/test_atomicalsconsensus_OP_KV_DELETE.py b/tests/lib/test_atomicalsconsensus_OP_KV_DELETE.py new file mode 100644 index 00000000..0a667514 --- /dev/null +++ b/tests/lib/test_atomicalsconsensus_OP_KV_DELETE.py @@ -0,0 +1,132 @@ +import pytest + +from electrumx.lib.coins import Bitcoin +from electrumx.lib.hash import hex_str_to_hash + +from electrumx.lib.avm.util import ( + encode_int_value, + RequestInterpretParams, + RequestBlockchainContext, + print_result_states, + ReactorContext, + RequestTxContext, + ScriptContext +) +from bitcointx.core.atomicalsconsensus import ( + ConsensusVerifyScriptAvmExecute, + AtomicalConsensusExecutionError +) +from bitcointx.core.script import ( + CScript +) + +from cbor2 import dumps, loads, CBORDecodeError + +coin = Bitcoin + +class MockLogger: + def debug(self, msg): + return + def info(self, msg): + return + def warning(self, msg): + return + +def mock_mint_fetcher(atomical_id): + return { + } + +mock_current_header = '000000209174c9f2757e2647733c9ab69c133b90257f85ce7e9c0100000000000000000096e490f16d161416bef825bd4716b0914f344be5835cf6a8f0de89288a6c440391af0566d3620317aca8414a' +mock_headers = { + '840012': mock_current_header +} +mock_blockchain_context = RequestBlockchainContext(mock_headers, 840012) +mock_rawtx = bytes.fromhex('02000000018e469f953413e8d865fcf1f47d759772aa05e8d78b1e4577a58edc8bd09344ff010000006b483045022100ce16646785907c919a1658496a85cf3f3d877d98ffca5eabd189524acc1de53b02205ac24a9e2855db3da26b840c82fddaf73a5a6eff4b5b78cadfb00584ad6bd5f3012102cbcad7b21fb5fb08ad55eb09e327b97f63e8c5e99b2faf9bb330545a5bd4602cfeffffff0203761700000000001976a91496d02c013f734a642871261324c58091a806c23188ac9ce52300000000001976a914a9a8d5aa3ec73688d0540d45be3842f4603705c588ac6e640800') +mock_tx, mock_tx_hash = coin.DESERIALIZER(mock_rawtx, 0).read_tx_and_hash() +mock_empty_reactor_context = ReactorContext(None, dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) + +def test_atomicalsconsensus_OP_KV_DELETE_1(): + payload = {} + request_tx_context = RequestTxContext(coin, mock_tx_hash, mock_tx, payload) + state_hash = bytes.fromhex('0000000000000000000000000000000000000000000000000000000000000000') + reactor_context = ReactorContext(state_hash, dumps({ + "02": { + "00": "68656c6c6f" + } + }), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) + blockchain_context = RequestBlockchainContext(mock_headers, 840012) + script_context = ScriptContext(CScript(bytes.fromhex('0177020004021234')), CScript(bytes.fromhex('f00177020004ef02123487750177020004f10177020004ed0087'))) + updated_reactor_state = ConsensusVerifyScriptAvmExecute(script_context, blockchain_context, request_tx_context, reactor_context) + + assert loads(updated_reactor_state.state) == { + "02": { + "00": "68656c6c6f" + } + } + state = loads(updated_reactor_state.state) + state_updates = loads(updated_reactor_state.state_updates) + state_deletes = loads(updated_reactor_state.state_deletes) + ft_incoming = loads(updated_reactor_state.ft_incoming) + nft_incoming = loads(updated_reactor_state.nft_incoming) + ft_balances = loads(updated_reactor_state.ft_balances) + ft_balances_updates = loads(updated_reactor_state.ft_balances_updates) + nft_balances = loads(updated_reactor_state.nft_balances) + nft_balances_updates = loads(updated_reactor_state.nft_balances_updates) + ft_withdraws = loads(updated_reactor_state.ft_withdraws) + nft_withdraws = loads(updated_reactor_state.nft_withdraws) + + assert updated_reactor_state.state_hash.hex() == '8d6b9400c2906a0ec3fd75ce432b18a43f7974e698efa99e736d32d2eb89f383' + assert len(state) == 1 + assert len(state_updates) == 0 + assert len(state_deletes) == 1 + assert state_deletes == {'77': {'0004': True}} + assert len(ft_incoming) == 0 + assert len(nft_incoming) == 0 + assert len(ft_balances) == 0 + assert len(ft_balances_updates) == 0 + assert len(nft_balances) == 0 + assert len(nft_balances_updates) == 0 + assert len(ft_withdraws) == 0 + assert len(nft_withdraws) == 0 + +def test_atomicalsconsensus_OP_KV_DELETE_2(): + payload = {} + request_tx_context = RequestTxContext(coin, mock_tx_hash, mock_tx, payload) + state_hash = bytes.fromhex('0000000000000000000000000000000000000000000000000000000000000000') + reactor_context = ReactorContext(state_hash, dumps({ + "02": { + "00": "68656c6c6f" + } + }), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) + blockchain_context = RequestBlockchainContext(mock_headers, 840012) + script_context = ScriptContext(CScript(bytes.fromhex('0177020004021234')), CScript(bytes.fromhex('f00177020004ef02123487750177020004f10177020004ed5200f10087'))) + updated_reactor_state = ConsensusVerifyScriptAvmExecute(script_context, blockchain_context, request_tx_context, reactor_context) + + state = loads(updated_reactor_state.state) + state_updates = loads(updated_reactor_state.state_updates) + state_deletes = loads(updated_reactor_state.state_deletes) + ft_incoming = loads(updated_reactor_state.ft_incoming) + nft_incoming = loads(updated_reactor_state.nft_incoming) + ft_balances = loads(updated_reactor_state.ft_balances) + ft_balances_updates = loads(updated_reactor_state.ft_balances_updates) + nft_balances = loads(updated_reactor_state.nft_balances) + nft_balances_updates = loads(updated_reactor_state.nft_balances_updates) + ft_withdraws = loads(updated_reactor_state.ft_withdraws) + nft_withdraws = loads(updated_reactor_state.nft_withdraws) + + assert state == {} + assert state_updates == {} + assert updated_reactor_state.state_hash.hex() == '6918702d8b95869ac1fce0522827d4641052a95cb34eb8dab4b2225594941b02' + assert len(state) == 0 + assert len(state_updates) == 0 + assert len(state_deletes) == 2 + assert state_deletes == {'02': {'00': True}, '77': {'0004': True}} + assert len(ft_incoming) == 0 + assert len(nft_incoming) == 0 + assert len(ft_balances) == 0 + assert len(ft_balances_updates) == 0 + assert len(nft_balances) == 0 + assert len(nft_balances_updates) == 0 + assert len(ft_withdraws) == 0 + assert len(nft_withdraws) == 0 + \ No newline at end of file diff --git a/tests/lib/test_atomicalsconsensus_OP_KV_EXISTS.py b/tests/lib/test_atomicalsconsensus_OP_KV_EXISTS.py new file mode 100644 index 00000000..fdbc4440 --- /dev/null +++ b/tests/lib/test_atomicalsconsensus_OP_KV_EXISTS.py @@ -0,0 +1,126 @@ +import pytest + +from electrumx.lib.coins import Bitcoin +from electrumx.lib.hash import hex_str_to_hash + +from electrumx.lib.avm.util import ( + encode_int_value, + RequestInterpretParams, + RequestBlockchainContext, + print_result_states, + ReactorContext, + RequestTxContext, + ScriptContext +) +from bitcointx.core.atomicalsconsensus import ( + ConsensusVerifyScriptAvmExecute, + AtomicalConsensusExecutionError +) +from bitcointx.core.script import ( + CScript +) + +from cbor2 import dumps, loads, CBORDecodeError + +coin = Bitcoin + +class MockLogger: + def debug(self, msg): + return + def info(self, msg): + return + def warning(self, msg): + return + +def mock_mint_fetcher(atomical_id): + return { + } + +mock_current_header = '000000209174c9f2757e2647733c9ab69c133b90257f85ce7e9c0100000000000000000096e490f16d161416bef825bd4716b0914f344be5835cf6a8f0de89288a6c440391af0566d3620317aca8414a' +mock_headers = { + '840012': mock_current_header +} +mock_blockchain_context = RequestBlockchainContext(mock_headers, 840012) +mock_rawtx = bytes.fromhex('02000000018e469f953413e8d865fcf1f47d759772aa05e8d78b1e4577a58edc8bd09344ff010000006b483045022100ce16646785907c919a1658496a85cf3f3d877d98ffca5eabd189524acc1de53b02205ac24a9e2855db3da26b840c82fddaf73a5a6eff4b5b78cadfb00584ad6bd5f3012102cbcad7b21fb5fb08ad55eb09e327b97f63e8c5e99b2faf9bb330545a5bd4602cfeffffff0203761700000000001976a91496d02c013f734a642871261324c58091a806c23188ac9ce52300000000001976a914a9a8d5aa3ec73688d0540d45be3842f4603705c588ac6e640800') +mock_tx, mock_tx_hash = coin.DESERIALIZER(mock_rawtx, 0).read_tx_and_hash() +mock_empty_reactor_context = ReactorContext(None, dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) + +def test_atomicalsconsensus_OP_KV_EXISTS_1(): + payload = {} + request_tx_context = RequestTxContext(coin, mock_tx_hash, mock_tx, payload) + state_hash = bytes.fromhex('0000000000000000000000000000000000000000000000000000000000000000') + reactor_context = ReactorContext(state_hash, dumps({ + '00': { + "012345": "68656c6c6f" + } + }), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) + blockchain_context = RequestBlockchainContext(mock_headers, 840012) + script_context = ScriptContext(CScript(), CScript(bytes.fromhex('010003012345ed'))) + updated_reactor_state = ConsensusVerifyScriptAvmExecute(script_context, blockchain_context, request_tx_context, reactor_context) + state = loads(updated_reactor_state.state) + assert state['00'] + assert state['00']['012345'] == '68656c6c6f' + assert updated_reactor_state.state == dumps({ + '00': { + "012345": "68656c6c6f" + } + }) + assert loads(updated_reactor_state.state_updates) == { + } + +def test_atomicalsconsensus_OP_KV_EXISTS_2_not_found(): + payload = {} + request_tx_context = RequestTxContext(coin, mock_tx_hash, mock_tx, payload) + state_hash = bytes.fromhex('0000000000000000000000000000000000000000000000000000000000000000') + reactor_context = ReactorContext(state_hash, dumps({ + '00': { + "012345": "68656c6c6f" + } + }), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) + blockchain_context = RequestBlockchainContext(mock_headers, 840012) + script_context = ScriptContext(CScript(), CScript(bytes.fromhex('018803012345ed0087'))) + updated_reactor_state = ConsensusVerifyScriptAvmExecute(script_context, blockchain_context, request_tx_context, reactor_context) + state = loads(updated_reactor_state.state) + assert state['00'] + assert state['00']['012345'] == '68656c6c6f' + assert updated_reactor_state.state == dumps({ + '00': { + "012345": "68656c6c6f" + } + }) + assert loads(updated_reactor_state.state_updates) == {} + +def test_atomicalsconsensus_OP_KV_EXISTS_3(): + with pytest.raises(AtomicalConsensusExecutionError) as exc: + payload = {} + request_tx_context = RequestTxContext(coin, mock_tx_hash, mock_tx, payload) + state_hash = bytes.fromhex('0000000000000000000000000000000000000000000000000000000000000000') + reactor_context = ReactorContext(state_hash, dumps({ + '00': { + "012345": "68656c6c6f" + } + }), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) + blockchain_context = RequestBlockchainContext(mock_headers, 840012) + script_context = ScriptContext(CScript(), CScript(bytes.fromhex('010003012346ed'))) + updated_reactor_state = ConsensusVerifyScriptAvmExecute(script_context, blockchain_context, request_tx_context, reactor_context) + assert exc.value.error_code == 0 + assert exc.value.script_error == 2 + assert exc.value.script_error_op_num == 2 + +def test_atomicalsconsensus_OP_KV_EXISTS_4(): + with pytest.raises(AtomicalConsensusExecutionError) as exc: + payload = {} + request_tx_context = RequestTxContext(coin, mock_tx_hash, mock_tx, payload) + state_hash = bytes.fromhex('0000000000000000000000000000000000000000000000000000000000000000') + reactor_context = ReactorContext(state_hash, dumps({ + '00': { + "012345": "68656c6c6f" + } + }), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) + blockchain_context = RequestBlockchainContext(mock_headers, 840012) + script_context = ScriptContext(CScript(), CScript(bytes.fromhex('015503012345ed'))) + updated_reactor_state = ConsensusVerifyScriptAvmExecute(script_context, blockchain_context, request_tx_context, reactor_context) + assert exc.value.error_code == 0 + assert exc.value.script_error == 2 + assert exc.value.script_error_op_num == 2 + \ No newline at end of file diff --git a/tests/lib/test_atomicalsconsensus_OP_KV_GET.py b/tests/lib/test_atomicalsconsensus_OP_KV_GET.py new file mode 100644 index 00000000..4d1d681a --- /dev/null +++ b/tests/lib/test_atomicalsconsensus_OP_KV_GET.py @@ -0,0 +1,78 @@ +import pytest + +from electrumx.lib.coins import Bitcoin +from electrumx.lib.hash import hex_str_to_hash + +from electrumx.lib.avm.util import ( + encode_int_value, + RequestInterpretParams, + RequestBlockchainContext, + print_result_states, + ReactorContext, + RequestTxContext, + ScriptContext +) +from bitcointx.core.atomicalsconsensus import ( + ConsensusVerifyScriptAvmExecute, + AtomicalConsensusExecutionError +) +from bitcointx.core.script import ( + CScript +) + +from cbor2 import dumps, loads, CBORDecodeError + +coin = Bitcoin + +class MockLogger: + def debug(self, msg): + return + def info(self, msg): + return + def warning(self, msg): + return + +def mock_mint_fetcher(atomical_id): + return { + } + +mock_current_header = '000000209174c9f2757e2647733c9ab69c133b90257f85ce7e9c0100000000000000000096e490f16d161416bef825bd4716b0914f344be5835cf6a8f0de89288a6c440391af0566d3620317aca8414a' +mock_headers = { + '840012': mock_current_header +} +mock_blockchain_context = RequestBlockchainContext(mock_headers, 840012) +mock_rawtx = bytes.fromhex('02000000018e469f953413e8d865fcf1f47d759772aa05e8d78b1e4577a58edc8bd09344ff010000006b483045022100ce16646785907c919a1658496a85cf3f3d877d98ffca5eabd189524acc1de53b02205ac24a9e2855db3da26b840c82fddaf73a5a6eff4b5b78cadfb00584ad6bd5f3012102cbcad7b21fb5fb08ad55eb09e327b97f63e8c5e99b2faf9bb330545a5bd4602cfeffffff0203761700000000001976a91496d02c013f734a642871261324c58091a806c23188ac9ce52300000000001976a914a9a8d5aa3ec73688d0540d45be3842f4603705c588ac6e640800') +mock_tx, mock_tx_hash = coin.DESERIALIZER(mock_rawtx, 0).read_tx_and_hash() +mock_empty_reactor_context = ReactorContext(None, dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) + +def test_atomicalsconsensus_OP_KV_GET_1(): + payload = {} + request_tx_context = RequestTxContext(coin, mock_tx_hash, mock_tx, payload) + state_hash = bytes.fromhex('0000000000000000000000000000000000000000000000000000000000000000') + reactor_context = ReactorContext(state_hash, dumps({ + "00": { + "01": "68656c6c6f" + } + }), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) + blockchain_context = RequestBlockchainContext(mock_headers, 840012) + script_context = ScriptContext(CScript(), CScript(bytes.fromhex('010051ef0568656c6c6f87'))) + updated_reactor_state = ConsensusVerifyScriptAvmExecute(script_context, blockchain_context, request_tx_context, reactor_context) + assert updated_reactor_state + +def test_atomicalsconsensus_OP_KV_GET_2(): + with pytest.raises(AtomicalConsensusExecutionError) as exc: + payload = {} + request_tx_context = RequestTxContext(coin, mock_tx_hash, mock_tx, payload) + state_hash = bytes.fromhex('0000000000000000000000000000000000000000000000000000000000000000') + reactor_context = ReactorContext(state_hash, dumps({ + "00": { + "0103": "68656c6c6f" + } + }), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) + blockchain_context = RequestBlockchainContext(mock_headers, 840012) + script_context = ScriptContext(CScript(), CScript(bytes.fromhex('010051ef0568656c6c6f87'))) + updated_reactor_state = ConsensusVerifyScriptAvmExecute(script_context, blockchain_context, request_tx_context, reactor_context) + assert exc.value.error_code == 0 + assert exc.value.script_error == 61 + assert exc.value.script_error_op_num == 2 + \ No newline at end of file diff --git a/tests/lib/test_atomicalsconsensus_OP_KV_PUT.py b/tests/lib/test_atomicalsconsensus_OP_KV_PUT.py new file mode 100644 index 00000000..fb721126 --- /dev/null +++ b/tests/lib/test_atomicalsconsensus_OP_KV_PUT.py @@ -0,0 +1,145 @@ +import pytest + +from electrumx.lib.coins import Bitcoin +from electrumx.lib.hash import hex_str_to_hash + +from electrumx.lib.avm.util import ( + encode_int_value, + RequestInterpretParams, + RequestBlockchainContext, + print_result_states, + ReactorContext, + RequestTxContext, + ScriptContext +) +from bitcointx.core.atomicalsconsensus import ( + ConsensusVerifyScriptAvmExecute, + AtomicalConsensusExecutionError +) +from bitcointx.core.script import ( + CScript +) + +from cbor2 import dumps, loads, CBORDecodeError + +coin = Bitcoin + +class MockLogger: + def debug(self, msg): + return + def info(self, msg): + return + def warning(self, msg): + return + +def mock_mint_fetcher(atomical_id): + return { + } + +mock_current_header = '000000209174c9f2757e2647733c9ab69c133b90257f85ce7e9c0100000000000000000096e490f16d161416bef825bd4716b0914f344be5835cf6a8f0de89288a6c440391af0566d3620317aca8414a' +mock_headers = { + '840012': mock_current_header +} +mock_blockchain_context = RequestBlockchainContext(mock_headers, 840012) +mock_rawtx = bytes.fromhex('02000000018e469f953413e8d865fcf1f47d759772aa05e8d78b1e4577a58edc8bd09344ff010000006b483045022100ce16646785907c919a1658496a85cf3f3d877d98ffca5eabd189524acc1de53b02205ac24a9e2855db3da26b840c82fddaf73a5a6eff4b5b78cadfb00584ad6bd5f3012102cbcad7b21fb5fb08ad55eb09e327b97f63e8c5e99b2faf9bb330545a5bd4602cfeffffff0203761700000000001976a91496d02c013f734a642871261324c58091a806c23188ac9ce52300000000001976a914a9a8d5aa3ec73688d0540d45be3842f4603705c588ac6e640800') +mock_tx, mock_tx_hash = coin.DESERIALIZER(mock_rawtx, 0).read_tx_and_hash() +mock_empty_reactor_context = ReactorContext(None, dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) + +def test_atomicalsconsensus_OP_KV_PUT_1(): + #with pytest.raises(AtomicalConsensusExecutionError) as exc: + payload = {} + request_tx_context = RequestTxContext(coin, mock_tx_hash, mock_tx, payload) + state_hash = bytes.fromhex('00000000000000000000000000000000000000000000000000000000000000') + reactor_context = ReactorContext(state_hash, dumps({ + "02": { + "00": "68656c6c6f" + } + }), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) + blockchain_context = RequestBlockchainContext(mock_headers, 840012) + script_context = ScriptContext(CScript(), CScript(bytes.fromhex('520100026f6ff0520100ef026f6f87'))) + result_context = ConsensusVerifyScriptAvmExecute(script_context, blockchain_context, request_tx_context, reactor_context) + assert result_context + assert loads(result_context.state) == {'02': {'00': '6f6f'}} + state = loads(result_context.state) + state_hash = result_context.state_hash + state = loads(result_context.state) + state_updates = loads(result_context.state_updates) + state_deletes = loads(result_context.state_deletes) + ft_incoming = loads(result_context.ft_incoming) + nft_incoming = loads(result_context.nft_incoming) + ft_balances = loads(result_context.ft_balances) + ft_balances_updates = loads(result_context.ft_balances_updates) + nft_balances = loads(result_context.nft_balances) + nft_balances_updates = loads(result_context.nft_balances_updates) + ft_withdraws = loads(result_context.ft_withdraws) + nft_withdraws = loads(result_context.nft_withdraws) + + assert result_context.state_hash.hex() == '26305552867599df1f263483988df47c05af348a0475421fe498ff2fdb1af08f' + assert loads(result_context.state_updates) == {'02': {'00': '6f6f'}} + assert loads(result_context.state_deletes) == {} + assert len(state) == 1 + assert len(state_updates) == 1 + assert len(state_deletes) == 0 + assert len(ft_incoming) == 0 + assert len(nft_incoming) == 0 + assert len(ft_balances) == 0 + assert len(ft_balances_updates) == 0 + assert len(nft_balances) == 0 + assert len(nft_balances_updates) == 0 + assert len(ft_withdraws) == 0 + assert len(nft_withdraws) == 0 + +def test_atomicalsconsensus_OP_KV_PUT_2(): + payload = {} + request_tx_context = RequestTxContext(coin, mock_tx_hash, mock_tx, payload) + state_hash = bytes.fromhex('0000000000000000000000000000000000000000000000000000000000000000') + reactor_context = ReactorContext(state_hash, dumps({ + "02": { + "00": "68656c6c6f" + } + }), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) + blockchain_context = RequestBlockchainContext(mock_headers, 840012) + script_context = ScriptContext(CScript(), CScript(bytes.fromhex('02123451026f6df051'))) + updated_reactor_state = ConsensusVerifyScriptAvmExecute(script_context, blockchain_context, request_tx_context, reactor_context) + assert loads(updated_reactor_state.state) == { + '1234': { + '01': '6f6d' + }, + "02": { + "00": "68656c6c6f" + } + } + state = loads(updated_reactor_state.state) + ft_incoming = loads(updated_reactor_state.ft_incoming) + nft_incoming = loads(updated_reactor_state.nft_incoming) + ft_balances = loads(updated_reactor_state.ft_balances) + nft_balances = loads(updated_reactor_state.nft_balances) + ft_withdraws = loads(updated_reactor_state.ft_withdraws) + nft_withdraws = loads(updated_reactor_state.nft_withdraws) + + assert updated_reactor_state.state_hash.hex() == '1c014b91b88083c4231abbd74923d24fa39e7d4e6487fb54d0ef972b5fa1b4ac' + assert len(state) == 2 + assert len(ft_incoming) == 0 + assert len(nft_incoming) == 0 + assert len(ft_balances) == 0 + assert len(nft_balances) == 0 + assert len(ft_withdraws) == 0 + assert len(nft_withdraws) == 0 + +def test_atomicalsconsensus_OP_KV_PUT_3(): + with pytest.raises(AtomicalConsensusExecutionError) as exc: + payload = {} + request_tx_context = RequestTxContext(coin, mock_tx_hash, mock_tx, payload) + state_hash = bytes.fromhex('0000000000000000000000000000000000000000000000000000000000000000') + reactor_context = ReactorContext(state_hash, dumps({ + "02": { + "00": "68656c6c6f" + } + }), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) + blockchain_context = RequestBlockchainContext(mock_headers, 840012) + script_context = ScriptContext(CScript(), CScript(bytes.fromhex('5404123456780488887777767e767e767e767e767e767e767e767e767e767e767e767e767ef051'))) + ConsensusVerifyScriptAvmExecute(script_context, blockchain_context, request_tx_context, reactor_context) + assert exc.value.error_code == 0 + assert exc.value.script_error == 5 + assert exc.value.script_error_op_num == 22 + \ No newline at end of file diff --git a/tests/lib/test_atomicalsconsensus_OP_LSHIFT_OP_RSHIFT.py b/tests/lib/test_atomicalsconsensus_OP_LSHIFT_OP_RSHIFT.py new file mode 100644 index 00000000..b87170b9 --- /dev/null +++ b/tests/lib/test_atomicalsconsensus_OP_LSHIFT_OP_RSHIFT.py @@ -0,0 +1,309 @@ +import pytest + +from electrumx.lib.coins import Bitcoin +from electrumx.lib.hash import hex_str_to_hash + +from electrumx.lib.avm.util import ( + encode_int_value, + RequestInterpretParams, + RequestBlockchainContext, + print_result_states, + ScriptContext, + encode_op_pushdata +) + +from electrumx.lib.avm.avm import ( + RequestBlockchainContext, + RequestTxContext, + ReactorContext +) +from bitcointx.core.atomicalsconsensus import ( + ConsensusVerifyScriptAvmExecute, + AtomicalConsensusExecutionError +) + +from bitcointx.core.script import ( + CScript +) + +from cbor2 import dumps, loads + +coin = Bitcoin + +class MockLogger: + def debug(self, msg): + return + def info(self, msg): + return + def warning(self, msg): + return + +mock_current_header = '000000209174c9f2757e2647733c9ab69c133b90257f85ce7e9c0100000000000000000096e490f16d161416bef825bd4716b0914f344be5835cf6a8f0de89288a6c440391af0566d3620317aca8414a' +mock_headers = { + '840012': mock_current_header +} +mock_blockchain_context = RequestBlockchainContext(mock_headers, 840012) +mock_rawtx = bytes.fromhex('02000000018e469f953413e8d865fcf1f47d759772aa05e8d78b1e4577a58edc8bd09344ff010000006b483045022100ce16646785907c919a1658496a85cf3f3d877d98ffca5eabd189524acc1de53b02205ac24a9e2855db3da26b840c82fddaf73a5a6eff4b5b78cadfb00584ad6bd5f3012102cbcad7b21fb5fb08ad55eb09e327b97f63e8c5e99b2faf9bb330545a5bd4602cfeffffff0203761700000000001976a91496d02c013f734a642871261324c58091a806c23188ac9ce52300000000001976a914a9a8d5aa3ec73688d0540d45be3842f4603705c588ac6e640800') +mock_tx, mock_tx_hash = coin.DESERIALIZER(mock_rawtx, 0).read_tx_and_hash() +mock_empty_reactor_context = ReactorContext(None, dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) + +def test_atomicalsconsensus_OP_LSHIFT_insufficient_stack(): + with pytest.raises(AtomicalConsensusExecutionError) as exc: + payload = {} + state_hash = bytes.fromhex('0000000000000000000000000000000000000000000000000000000000000000') + request_tx_context = RequestTxContext(coin, mock_tx_hash, mock_tx, payload) + reactor_context = ReactorContext(state_hash, dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) + blockchain_context = RequestBlockchainContext(mock_headers, 840012) + script_context = ScriptContext(CScript(), CScript(bytes.fromhex('0098'))) + updated_reactor_state = ConsensusVerifyScriptAvmExecute(script_context, blockchain_context, request_tx_context, reactor_context) + assert updated_reactor_state + assert exc.value.error_code == 0 + assert exc.value.script_error == 24 + assert exc.value.script_error_op_num == 1 + +def test_atomicalsconsensus_OP_RSHIFT_insufficient_stack(): + with pytest.raises(AtomicalConsensusExecutionError) as exc: + payload = {} + state_hash = bytes.fromhex('0000000000000000000000000000000000000000000000000000000000000000') + request_tx_context = RequestTxContext(coin, mock_tx_hash, mock_tx, payload) + reactor_context = ReactorContext(state_hash, dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) + blockchain_context = RequestBlockchainContext(mock_headers, 840012) + script_context = ScriptContext(CScript(), CScript(bytes.fromhex('0099'))) + updated_reactor_state = ConsensusVerifyScriptAvmExecute(script_context, blockchain_context, request_tx_context, reactor_context) + assert updated_reactor_state + assert exc.value.error_code == 0 + assert exc.value.script_error == 24 + assert exc.value.script_error_op_num == 1 + +def test_atomicalsconsensus_OP_LSHIFT_zero_success(): + payload = {} + state_hash = bytes.fromhex('0000000000000000000000000000000000000000000000000000000000000000') + request_tx_context = RequestTxContext(coin, mock_tx_hash, mock_tx, payload) + reactor_context = ReactorContext(state_hash, dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) + blockchain_context = RequestBlockchainContext(mock_headers, 840012) + script_context = ScriptContext(CScript(), CScript(bytes.fromhex('0000980087'))) + updated_reactor_state = ConsensusVerifyScriptAvmExecute(script_context, blockchain_context, request_tx_context, reactor_context) + assert updated_reactor_state + +def test_atomicalsconsensus_OP_RSHIFT_zero_success(): + payload = {} + state_hash = bytes.fromhex('0000000000000000000000000000000000000000000000000000000000000000') + request_tx_context = RequestTxContext(coin, mock_tx_hash, mock_tx, payload) + reactor_context = ReactorContext(state_hash, dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) + blockchain_context = RequestBlockchainContext(mock_headers, 840012) + script_context = ScriptContext(CScript(), CScript(bytes.fromhex('0000990087'))) + updated_reactor_state = ConsensusVerifyScriptAvmExecute(script_context, blockchain_context, request_tx_context, reactor_context) + assert updated_reactor_state + +def test_atomicalsconsensus_OP_LSHIFT_1_shift_zero_success(): + payload = {} + state_hash = bytes.fromhex('0000000000000000000000000000000000000000000000000000000000000000') + request_tx_context = RequestTxContext(coin, mock_tx_hash, mock_tx, payload) + reactor_context = ReactorContext(state_hash, dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) + blockchain_context = RequestBlockchainContext(mock_headers, 840012) + script_context = ScriptContext(CScript(), CScript(bytes.fromhex('0051980087'))) + updated_reactor_state = ConsensusVerifyScriptAvmExecute(script_context, blockchain_context, request_tx_context, reactor_context) + assert updated_reactor_state + +def test_atomicalsconsensus_OP_RSHIFT_1_shift_zero_success(): + payload = {} + state_hash = bytes.fromhex('0000000000000000000000000000000000000000000000000000000000000000') + request_tx_context = RequestTxContext(coin, mock_tx_hash, mock_tx, payload) + reactor_context = ReactorContext(state_hash, dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) + blockchain_context = RequestBlockchainContext(mock_headers, 840012) + script_context = ScriptContext(CScript(), CScript(bytes.fromhex('0051990087'))) + updated_reactor_state = ConsensusVerifyScriptAvmExecute(script_context, blockchain_context, request_tx_context, reactor_context) + assert updated_reactor_state + +def test_atomicalsconsensus_OP_LSHIFT_minus_1_shift_zero_fail(): + with pytest.raises(AtomicalConsensusExecutionError) as exc: + payload = {} + state_hash = bytes.fromhex('0000000000000000000000000000000000000000000000000000000000000000') + request_tx_context = RequestTxContext(coin, mock_tx_hash, mock_tx, payload) + reactor_context = ReactorContext(state_hash, dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) + blockchain_context = RequestBlockchainContext(mock_headers, 840012) + script_context = ScriptContext(CScript(), CScript(bytes.fromhex('00518f980087'))) + ConsensusVerifyScriptAvmExecute(script_context, blockchain_context, request_tx_context, reactor_context) + assert exc.value.error_code == 0 + assert exc.value.script_error == 12 + assert exc.value.script_error_op_num == 3 + +def test_atomicalsconsensus_OP_RSHIFT_minus_1_shift_zero_fail(): + with pytest.raises(AtomicalConsensusExecutionError) as exc: + payload = {} + state_hash = bytes.fromhex('0000000000000000000000000000000000000000000000000000000000000000') + request_tx_context = RequestTxContext(coin, mock_tx_hash, mock_tx, payload) + reactor_context = ReactorContext(state_hash, dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) + blockchain_context = RequestBlockchainContext(mock_headers, 840012) + script_context = ScriptContext(CScript(), CScript(bytes.fromhex('00518f990087'))) + ConsensusVerifyScriptAvmExecute(script_context, blockchain_context, request_tx_context, reactor_context) + assert exc.value.error_code == 0 + assert exc.value.script_error == 12 + assert exc.value.script_error_op_num == 3 + +def test_atomicalsconsensus_OP_LSHIFT_1_shift_32_success(): + payload = {} + state_hash = bytes.fromhex('0000000000000000000000000000000000000000000000000000000000000000') + request_tx_context = RequestTxContext(coin, mock_tx_hash, mock_tx, payload) + reactor_context = ReactorContext(state_hash, dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) + blockchain_context = RequestBlockchainContext(mock_headers, 840012) + script_context = ScriptContext(CScript(), CScript(bytes.fromhex('01205198014087'))) + updated_reactor_state = ConsensusVerifyScriptAvmExecute(script_context, blockchain_context, request_tx_context, reactor_context) + assert updated_reactor_state + +def test_atomicalsconsensus_OP_LSHIFT_loop_shift_success(): + payload = {} + state_hash = bytes.fromhex('0000000000000000000000000000000000000000000000000000000000000000') + request_tx_context = RequestTxContext(coin, mock_tx_hash, mock_tx, payload) + reactor_context = ReactorContext(state_hash, dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) + blockchain_context = RequestBlockchainContext(mock_headers, 840012) + max_int = 9223372036854775807 + test_items = [ + { + 'num': 0, + 'shift': 1, + 'result': '00' + }, + { + 'num': 1, + 'shift': 1, + 'result': '52' + }, + { + 'num': 2, + 'shift': 1, + 'result': '54' + }, + { + 'num': 4, + 'shift': 1, + 'result': '58' + }, + { + 'num': 8, + 'shift': 2, + 'result': '0120' + }, + { + 'num': 1024, + 'shift': 1, + 'result': '020008' + }, + { + 'num': 1024, + 'shift': 2, + 'result': '020010' + }, + { + 'num': 1024, + 'shift': 3, + 'result': '020020' + }, + { + 'num': 1024, + 'shift': 4, + 'result': '020040' + }, + { + 'num': 1024, + 'shift': 5, + 'result': '020080' + }, + { + 'num': 1024, + 'shift': 6, + 'result': '020100' + }, + { + 'num': max_int, + 'shift': 1, + 'result': '08fffffffffffffefe' + } + ] + + for item in test_items: + num_enc = encode_int_value(item['num'], True) + shift_enc = encode_int_value(item['shift'], True) + script_context = ScriptContext(CScript(), CScript(bytes.fromhex(num_enc + shift_enc + '98' + item['result'] + '87'))) + updated_reactor_state = ConsensusVerifyScriptAvmExecute(script_context, blockchain_context, request_tx_context, reactor_context) + assert updated_reactor_state + +def test_atomicalsconsensus_OP_RSHIFT_1_shift_32_success(): + payload = {} + state_hash = bytes.fromhex('0000000000000000000000000000000000000000000000000000000000000000') + request_tx_context = RequestTxContext(coin, mock_tx_hash, mock_tx, payload) + reactor_context = ReactorContext(state_hash, dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) + blockchain_context = RequestBlockchainContext(mock_headers, 840012) + script_context = ScriptContext(CScript(), CScript(bytes.fromhex('012051996087'))) + updated_reactor_state = ConsensusVerifyScriptAvmExecute(script_context, blockchain_context, request_tx_context, reactor_context) + assert updated_reactor_state + + +def test_atomicalsconsensus_OP_RSHIFT_loop_shift_success(): + payload = {} + state_hash = bytes.fromhex('0000000000000000000000000000000000000000000000000000000000000000') + request_tx_context = RequestTxContext(coin, mock_tx_hash, mock_tx, payload) + reactor_context = ReactorContext(state_hash, dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) + blockchain_context = RequestBlockchainContext(mock_headers, 840012) + + max_int = 9223372036854775807 + test_items = [ + { + 'num': 0, + 'shift': 1, + 'result': '00' + }, + { + 'num': 1, + 'shift': 1, + 'result': '00' + }, + { + 'num': 2, + 'shift': 1, + 'result': '51' + }, + { + 'num': 4, + 'shift': 1, + 'result': '52' + }, + { + 'num': 8, + 'shift': 2, + 'result': '52' + }, + { + 'num': 1024, + 'shift': 1, + 'result': '020002' + }, + { + 'num': 1024, + 'shift': 2, + 'result': '020001' + }, + { + 'num': 1024, + 'shift': 3, + 'result': '00' + }, + { + 'num': 1024, + 'shift': 4, + 'result': '00' + }, + { + 'num': max_int, + 'shift': 1, + 'result': '087fffffffffffffbf' + } + + ] + + for item in test_items: + num_enc = encode_int_value(item['num'], True) + shift_enc = encode_int_value(item['shift'], True) + script_context = ScriptContext(CScript(), CScript(bytes.fromhex(num_enc + shift_enc + '9981' + item['result'] + '87'))) + updated_reactor_state = ConsensusVerifyScriptAvmExecute(script_context, blockchain_context, request_tx_context, reactor_context) + assert updated_reactor_state \ No newline at end of file diff --git a/tests/lib/test_atomicalsconsensus_OP_NFT_COUNT.py b/tests/lib/test_atomicalsconsensus_OP_NFT_COUNT.py new file mode 100644 index 00000000..d2bba0c8 --- /dev/null +++ b/tests/lib/test_atomicalsconsensus_OP_NFT_COUNT.py @@ -0,0 +1,123 @@ +import pytest + +from electrumx.lib.coins import Bitcoin +from electrumx.lib.hash import hex_str_to_hash + +from electrumx.lib.avm.util import ( + encode_int_value, + RequestInterpretParams, + RequestBlockchainContext, + print_result_states, + ScriptContext +) + +from electrumx.lib.avm.avm import ( + RequestBlockchainContext, + RequestTxContext, + ReactorContext +) +from bitcointx.core.atomicalsconsensus import ( + ConsensusVerifyScriptAvmExecute +) + +from bitcointx.core.script import ( + CScript +) + +from cbor2 import dumps + +coin = Bitcoin + +class MockLogger: + def debug(self, msg): + return + def info(self, msg): + return + def warning(self, msg): + return + +mock_current_header = '000000209174c9f2757e2647733c9ab69c133b90257f85ce7e9c0100000000000000000096e490f16d161416bef825bd4716b0914f344be5835cf6a8f0de89288a6c440391af0566d3620317aca8414a' +mock_headers = { + '840012': mock_current_header +} +mock_blockchain_context = RequestBlockchainContext(mock_headers, 840012) +mock_rawtx = bytes.fromhex('02000000018e469f953413e8d865fcf1f47d759772aa05e8d78b1e4577a58edc8bd09344ff010000006b483045022100ce16646785907c919a1658496a85cf3f3d877d98ffca5eabd189524acc1de53b02205ac24a9e2855db3da26b840c82fddaf73a5a6eff4b5b78cadfb00584ad6bd5f3012102cbcad7b21fb5fb08ad55eb09e327b97f63e8c5e99b2faf9bb330545a5bd4602cfeffffff0203761700000000001976a91496d02c013f734a642871261324c58091a806c23188ac9ce52300000000001976a914a9a8d5aa3ec73688d0540d45be3842f4603705c588ac6e640800') +mock_tx, mock_tx_hash = coin.DESERIALIZER(mock_rawtx, 0).read_tx_and_hash() +mock_empty_reactor_context = ReactorContext(None, dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) + +def test_atomicalsconsensus_OP_NFT_COUNT_empty_success1(): + payload = {} + state_hash = bytes.fromhex('0000000000000000000000000000000000000000000000000000000000000000') + request_tx_context = RequestTxContext(coin, mock_tx_hash, mock_tx, payload) + reactor_context = ReactorContext(state_hash, dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) + blockchain_context = RequestBlockchainContext(mock_headers, 840012) + script_context = ScriptContext(CScript(), CScript(bytes.fromhex('00f90087'))) + updated_reactor_state = ConsensusVerifyScriptAvmExecute(script_context, blockchain_context, request_tx_context, reactor_context) + assert updated_reactor_state + +def test_atomicalsconsensus_OP_NFT_COUNT_nonempty_success1(): + nft_balances = {} + sample_token_id1 = '012345678901234567890123456789012345678901234567890123456789000000000000' + nft_balances[sample_token_id1] = True + state_hash = bytes.fromhex('0000000000000000000000000000000000000000000000000000000000000000') + reactor_context = ReactorContext(state_hash, dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps(nft_balances), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) + payload = {} + request_tx_context = RequestTxContext(coin, mock_tx_hash, mock_tx, payload) + blockchain_context = RequestBlockchainContext(mock_headers, 840012) + script_context = ScriptContext(CScript(), CScript(bytes.fromhex('00f95187'))) + updated_reactor_state = ConsensusVerifyScriptAvmExecute(script_context, blockchain_context, request_tx_context, reactor_context) + assert updated_reactor_state + +def test_atomicalsconsensus_OP_NFT_COUNT_nonempty_success2(): + nft_balances = {} + sample_token_id1 = '012345678901234567890123456789012345678901234567890123456789000000000000' + sample_token_id2 = '112345678901234567890123456789012345678901234567890123456789000000000001' + nft_balances[sample_token_id1] = True + nft_balances[sample_token_id2] = True + state_hash = bytes.fromhex('0000000000000000000000000000000000000000000000000000000000000000') + reactor_context = ReactorContext(state_hash, dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps(nft_balances), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) + payload = {} + request_tx_context = RequestTxContext(coin, mock_tx_hash, mock_tx, payload) + blockchain_context = RequestBlockchainContext(mock_headers, 840012) + script_context = ScriptContext(CScript(), CScript(bytes.fromhex('00f95287'))) + updated_reactor_state = ConsensusVerifyScriptAvmExecute(script_context, blockchain_context, request_tx_context, reactor_context) + assert updated_reactor_state + + +def test_atomicalsconsensus_OP_NFT_COUNT_incoming_empty_success1(): + payload = {} + state_hash = bytes.fromhex('0000000000000000000000000000000000000000000000000000000000000000') + request_tx_context = RequestTxContext(coin, mock_tx_hash, mock_tx, payload) + reactor_context = ReactorContext(state_hash, dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) + blockchain_context = RequestBlockchainContext(mock_headers, 840012) + script_context = ScriptContext(CScript(), CScript(bytes.fromhex('51f90087'))) + updated_reactor_state = ConsensusVerifyScriptAvmExecute(script_context, blockchain_context, request_tx_context, reactor_context) + assert updated_reactor_state + +def test_atomicalsconsensus_OP_NFT_COUNT_incoming_nonempty_success1(): + nft_balances = {} + sample_token_id1 = '012345678901234567890123456789012345678901234567890123456789000000000000' + nft_balances[sample_token_id1] = True + state_hash = bytes.fromhex('0000000000000000000000000000000000000000000000000000000000000000') + reactor_context = ReactorContext(state_hash, dumps({}), dumps({}), dumps({}), dumps(nft_balances), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) + payload = {} + request_tx_context = RequestTxContext(coin, mock_tx_hash, mock_tx, payload) + blockchain_context = RequestBlockchainContext(mock_headers, 840012) + script_context = ScriptContext(CScript(), CScript(bytes.fromhex('51f95187'))) + updated_reactor_state = ConsensusVerifyScriptAvmExecute(script_context, blockchain_context, request_tx_context, reactor_context) + assert updated_reactor_state + +def test_atomicalsconsensus_OP_NFT_COUNT_incoming_nonempty_success2(): + nft_balances = {} + sample_token_id1 = '012345678901234567890123456789012345678901234567890123456789000000000000' + sample_token_id2 = '112345678901234567890123456789012345678901234567890123456789000000000001' + nft_balances[sample_token_id1] = True + nft_balances[sample_token_id2] = True + state_hash = bytes.fromhex('0000000000000000000000000000000000000000000000000000000000000000') + reactor_context = ReactorContext(state_hash, dumps({}), dumps({}), dumps({}), dumps(nft_balances), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) + payload = {} + request_tx_context = RequestTxContext(coin, mock_tx_hash, mock_tx, payload) + blockchain_context = RequestBlockchainContext(mock_headers, 840012) + script_context = ScriptContext(CScript(), CScript(bytes.fromhex('51f95287'))) + updated_reactor_state = ConsensusVerifyScriptAvmExecute(script_context, blockchain_context, request_tx_context, reactor_context) + assert updated_reactor_state \ No newline at end of file diff --git a/tests/lib/test_atomicalsconsensus_OP_NFT_EXISTS.py b/tests/lib/test_atomicalsconsensus_OP_NFT_EXISTS.py new file mode 100644 index 00000000..8ebabb82 --- /dev/null +++ b/tests/lib/test_atomicalsconsensus_OP_NFT_EXISTS.py @@ -0,0 +1,108 @@ +import pytest + +from electrumx.lib.coins import Bitcoin +from electrumx.lib.hash import hex_str_to_hash + +from electrumx.lib.avm.util import ( + encode_int_value, + RequestInterpretParams, + RequestBlockchainContext, + print_result_states, + ScriptContext, + encode_op_pushdata +) + +from electrumx.lib.avm.avm import ( + RequestBlockchainContext, + RequestTxContext, + ReactorContext +) +from bitcointx.core.atomicalsconsensus import ( + ConsensusVerifyScriptAvmExecute, + AtomicalConsensusExecutionError +) + +from bitcointx.core.script import ( + CScript +) + +from cbor2 import dumps + +coin = Bitcoin + +class MockLogger: + def debug(self, msg): + return + def info(self, msg): + return + def warning(self, msg): + return + +mock_current_header = '000000209174c9f2757e2647733c9ab69c133b90257f85ce7e9c0100000000000000000096e490f16d161416bef825bd4716b0914f344be5835cf6a8f0de89288a6c440391af0566d3620317aca8414a' +mock_headers = { + '840012': mock_current_header +} +mock_blockchain_context = RequestBlockchainContext(mock_headers, 840012) +mock_rawtx = bytes.fromhex('02000000018e469f953413e8d865fcf1f47d759772aa05e8d78b1e4577a58edc8bd09344ff010000006b483045022100ce16646785907c919a1658496a85cf3f3d877d98ffca5eabd189524acc1de53b02205ac24a9e2855db3da26b840c82fddaf73a5a6eff4b5b78cadfb00584ad6bd5f3012102cbcad7b21fb5fb08ad55eb09e327b97f63e8c5e99b2faf9bb330545a5bd4602cfeffffff0203761700000000001976a91496d02c013f734a642871261324c58091a806c23188ac9ce52300000000001976a914a9a8d5aa3ec73688d0540d45be3842f4603705c588ac6e640800') +mock_tx, mock_tx_hash = coin.DESERIALIZER(mock_rawtx, 0).read_tx_and_hash() +mock_empty_reactor_context = ReactorContext(None, dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) + +def test_atomicalsconsensus_OP_NFT_EXISTS_not_exists1(): + payload = {} + sample_token_id1 = '012345678901234567890123456789012345678901234567890123456789000000000000' + sample_token_id1_bytes = bytearray.fromhex(sample_token_id1) + sample_token_id1_bytes.reverse() + sample_token_id1_encoded = encode_op_pushdata(sample_token_id1_bytes) + state_hash = bytes.fromhex('0000000000000000000000000000000000000000000000000000000000000000') + request_tx_context = RequestTxContext(coin, mock_tx_hash, mock_tx, payload) + reactor_context = ReactorContext(state_hash, dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) + blockchain_context = RequestBlockchainContext(mock_headers, 840012) + script_context = ScriptContext(CScript(), CScript(bytes.fromhex(sample_token_id1_encoded.hex() + '00f80087'))) + updated_reactor_state = ConsensusVerifyScriptAvmExecute(script_context, blockchain_context, request_tx_context, reactor_context) + assert updated_reactor_state + +def test_atomicalsconsensus_OP_NFT_EXISTS_exists1(): + balances = {} + sample_token_id1 = '012345678901234567890123456789012345678901234567890123456789000000000000' + sample_token_id1_bytes = bytearray.fromhex(sample_token_id1) + sample_token_id1_bytes.reverse() + sample_token_id1_encoded = encode_op_pushdata(sample_token_id1_bytes) + balances[sample_token_id1] = True + state_hash = bytes.fromhex('0000000000000000000000000000000000000000000000000000000000000000') + reactor_context = ReactorContext(state_hash, dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps(balances), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) + payload = {} + request_tx_context = RequestTxContext(coin, mock_tx_hash, mock_tx, payload) + blockchain_context = RequestBlockchainContext(mock_headers, 840012) + script_context = ScriptContext(CScript(), CScript(bytes.fromhex(sample_token_id1_encoded.hex() + '00f85187'))) + updated_reactor_state = ConsensusVerifyScriptAvmExecute(script_context, blockchain_context, request_tx_context, reactor_context) + assert updated_reactor_state + +def test_atomicalsconsensus_OP_NFT_EXISTS_incoming_not_exists1(): + payload = {} + sample_token_id1 = '012345678901234567890123456789012345678901234567890123456789000000000000' + sample_token_id1_bytes = bytearray.fromhex(sample_token_id1) + sample_token_id1_bytes.reverse() + sample_token_id1_encoded = encode_op_pushdata(sample_token_id1_bytes) + state_hash = bytes.fromhex('0000000000000000000000000000000000000000000000000000000000000000') + request_tx_context = RequestTxContext(coin, mock_tx_hash, mock_tx, payload) + reactor_context = ReactorContext(state_hash, dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) + blockchain_context = RequestBlockchainContext(mock_headers, 840012) + script_context = ScriptContext(CScript(), CScript(bytes.fromhex(sample_token_id1_encoded.hex() + '51f80087'))) + updated_reactor_state = ConsensusVerifyScriptAvmExecute(script_context, blockchain_context, request_tx_context, reactor_context) + assert updated_reactor_state + +def test_atomicalsconsensus_OP_NFT_EXISTS_incoming_exists1(): + balances = {} + sample_token_id1 = '012345678901234567890123456789012345678901234567890123456789000000000000' + sample_token_id1_bytes = bytearray.fromhex(sample_token_id1) + sample_token_id1_bytes.reverse() + sample_token_id1_encoded = encode_op_pushdata(sample_token_id1_bytes) + balances[sample_token_id1] = True + state_hash = bytes.fromhex('0000000000000000000000000000000000000000000000000000000000000000') + reactor_context = ReactorContext(state_hash, dumps({}), dumps({}), dumps({}), dumps(balances), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) + payload = {} + request_tx_context = RequestTxContext(coin, mock_tx_hash, mock_tx, payload) + blockchain_context = RequestBlockchainContext(mock_headers, 840012) + script_context = ScriptContext(CScript(), CScript(bytes.fromhex(sample_token_id1_encoded.hex() + '51f85187'))) + updated_reactor_state = ConsensusVerifyScriptAvmExecute(script_context, blockchain_context, request_tx_context, reactor_context) + assert updated_reactor_state \ No newline at end of file diff --git a/tests/lib/test_atomicalsconsensus_OP_NFT_ITEM.py b/tests/lib/test_atomicalsconsensus_OP_NFT_ITEM.py new file mode 100644 index 00000000..439b4c33 --- /dev/null +++ b/tests/lib/test_atomicalsconsensus_OP_NFT_ITEM.py @@ -0,0 +1,144 @@ +import pytest + +from electrumx.lib.coins import Bitcoin +from electrumx.lib.hash import hex_str_to_hash + +from electrumx.lib.avm.util import ( + encode_int_value, + RequestInterpretParams, + RequestBlockchainContext, + print_result_states, + ScriptContext, + encode_op_pushdata +) + +from electrumx.lib.avm.avm import ( + RequestBlockchainContext, + RequestTxContext, + ReactorContext +) +from bitcointx.core.atomicalsconsensus import ( + ConsensusVerifyScriptAvmExecute, + AtomicalConsensusExecutionError +) + +from bitcointx.core.script import ( + CScript +) + +from cbor2 import dumps + +coin = Bitcoin + +class MockLogger: + def debug(self, msg): + return + def info(self, msg): + return + def warning(self, msg): + return + +mock_current_header = '000000209174c9f2757e2647733c9ab69c133b90257f85ce7e9c0100000000000000000096e490f16d161416bef825bd4716b0914f344be5835cf6a8f0de89288a6c440391af0566d3620317aca8414a' +mock_headers = { + '840012': mock_current_header +} +mock_blockchain_context = RequestBlockchainContext(mock_headers, 840012) +mock_rawtx = bytes.fromhex('02000000018e469f953413e8d865fcf1f47d759772aa05e8d78b1e4577a58edc8bd09344ff010000006b483045022100ce16646785907c919a1658496a85cf3f3d877d98ffca5eabd189524acc1de53b02205ac24a9e2855db3da26b840c82fddaf73a5a6eff4b5b78cadfb00584ad6bd5f3012102cbcad7b21fb5fb08ad55eb09e327b97f63e8c5e99b2faf9bb330545a5bd4602cfeffffff0203761700000000001976a91496d02c013f734a642871261324c58091a806c23188ac9ce52300000000001976a914a9a8d5aa3ec73688d0540d45be3842f4603705c588ac6e640800') +mock_tx, mock_tx_hash = coin.DESERIALIZER(mock_rawtx, 0).read_tx_and_hash() +mock_empty_reactor_context = ReactorContext(None, dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) + +def test_atomicalsconsensus_OP_NFT_ITEM_out_of_bounds(): + with pytest.raises(AtomicalConsensusExecutionError) as exc: + payload = {} + state_hash = bytes.fromhex('0000000000000000000000000000000000000000000000000000000000000000') + request_tx_context = RequestTxContext(coin, mock_tx_hash, mock_tx, payload) + reactor_context = ReactorContext(state_hash, dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) + blockchain_context = RequestBlockchainContext(mock_headers, 840012) + script_context = ScriptContext(CScript(), CScript(bytes.fromhex('0000fa'))) + updated_reactor_state = ConsensusVerifyScriptAvmExecute(script_context, blockchain_context, request_tx_context, reactor_context) + assert updated_reactor_state + assert exc.value.error_code == 0 + assert exc.value.script_error == 68 + assert exc.value.script_error_op_num == 2 + +def test_atomicalsconsensus_OP_NFT_ITEM_incoming_out_of_bounds(): + with pytest.raises(AtomicalConsensusExecutionError) as exc: + payload = {} + state_hash = bytes.fromhex('0000000000000000000000000000000000000000000000000000000000000000') + request_tx_context = RequestTxContext(coin, mock_tx_hash, mock_tx, payload) + reactor_context = ReactorContext(state_hash, dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) + blockchain_context = RequestBlockchainContext(mock_headers, 840012) + script_context = ScriptContext(CScript(), CScript(bytes.fromhex('5251fa'))) + updated_reactor_state = ConsensusVerifyScriptAvmExecute(script_context, blockchain_context, request_tx_context, reactor_context) + assert updated_reactor_state + assert exc.value.error_code == 0 + assert exc.value.script_error == 68 + assert exc.value.script_error_op_num == 2 + +def test_atomicalsconsensus_OP_NFT_ITEM_success1(): + balances = {} + sample_token_id1 = '012345678901234567890123456789012345678901234567890123456789000000000000' + sample_token_id1_bytes = bytearray.fromhex(sample_token_id1) + sample_token_id1_bytes.reverse() + sample_token_id1_encoded = encode_op_pushdata(sample_token_id1_bytes) + balances[sample_token_id1] = True + state_hash = bytes.fromhex('0000000000000000000000000000000000000000000000000000000000000000') + reactor_context = ReactorContext(state_hash, dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps(balances), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) + payload = {} + request_tx_context = RequestTxContext(coin, mock_tx_hash, mock_tx, payload) + blockchain_context = RequestBlockchainContext(mock_headers, 840012) + script_context = ScriptContext(CScript(), CScript(bytes.fromhex('0000fa' + sample_token_id1_encoded.hex() + '87'))) + updated_reactor_state = ConsensusVerifyScriptAvmExecute(script_context, blockchain_context, request_tx_context, reactor_context) + assert updated_reactor_state + +def test_atomicalsconsensus_OP_NFT_ITEM_success2(): + balances = {} + sample_ft_token_id1 = '012345678901234567890123456789012345678901234567890123456789000000000000' + sample_ft_token_id2 = '112345678901234567890123456789012345678901234567890123456789000000000001' + balances[sample_ft_token_id1] = True + balances[sample_ft_token_id2] = True + sample_ft_token_id2_bytes = bytearray.fromhex(sample_ft_token_id2) + sample_ft_token_id2_bytes.reverse() + sample_ft_token_id2_encoded = encode_op_pushdata(sample_ft_token_id2_bytes) + state_hash = bytes.fromhex('0000000000000000000000000000000000000000000000000000000000000000') + reactor_context = ReactorContext(state_hash, dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps(balances), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) + payload = {} + request_tx_context = RequestTxContext(coin, mock_tx_hash, mock_tx, payload) + blockchain_context = RequestBlockchainContext(mock_headers, 840012) + script_context = ScriptContext(CScript(), CScript(bytes.fromhex('5100fa' + sample_ft_token_id2_encoded.hex() + '87'))) + updated_reactor_state = ConsensusVerifyScriptAvmExecute(script_context, blockchain_context, request_tx_context, reactor_context) + assert updated_reactor_state + +def test_atomicalsconsensus_OP_NFT_ITEM_incoming_success1(): + balances = {} + sample_token_id1 = '012345678901234567890123456789012345678901234567890123456789000000000000' + sample_token_id1_bytes = bytearray.fromhex(sample_token_id1) + sample_token_id1_bytes.reverse() + sample_token_id1_encoded = encode_op_pushdata(sample_token_id1_bytes) + balances[sample_token_id1] = True + state_hash = bytes.fromhex('0000000000000000000000000000000000000000000000000000000000000000') + reactor_context = ReactorContext(state_hash, dumps({}), dumps({}), dumps({}), dumps(balances), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) + payload = {} + request_tx_context = RequestTxContext(coin, mock_tx_hash, mock_tx, payload) + blockchain_context = RequestBlockchainContext(mock_headers, 840012) + script_context = ScriptContext(CScript(), CScript(bytes.fromhex('0051fa' + sample_token_id1_encoded.hex() + '87'))) + updated_reactor_state = ConsensusVerifyScriptAvmExecute(script_context, blockchain_context, request_tx_context, reactor_context) + assert updated_reactor_state + +def test_atomicalsconsensus_OP_NFT_ITEM_incoming_success2(): + balances = {} + sample_ft_token_id1 = '012345678901234567890123456789012345678901234567890123456789000000000000' + sample_ft_token_id2 = '112345678901234567890123456789012345678901234567890123456789000000000001' + balances[sample_ft_token_id1] = True + balances[sample_ft_token_id2] = True + sample_ft_token_id2_bytes = bytearray.fromhex(sample_ft_token_id2) + sample_ft_token_id2_bytes.reverse() + sample_ft_token_id2_encoded = encode_op_pushdata(sample_ft_token_id2_bytes) + state_hash = bytes.fromhex('0000000000000000000000000000000000000000000000000000000000000000') + reactor_context = ReactorContext(state_hash, dumps({}), dumps({}), dumps({}), dumps(balances), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) + payload = {} + request_tx_context = RequestTxContext(coin, mock_tx_hash, mock_tx, payload) + blockchain_context = RequestBlockchainContext(mock_headers, 840012) + script_context = ScriptContext(CScript(), CScript(bytes.fromhex('5151fa' + sample_ft_token_id2_encoded.hex() + '87'))) + updated_reactor_state = ConsensusVerifyScriptAvmExecute(script_context, blockchain_context, request_tx_context, reactor_context) + assert updated_reactor_state diff --git a/tests/lib/test_atomicalsconsensus_OP_NFT_PUT.py b/tests/lib/test_atomicalsconsensus_OP_NFT_PUT.py new file mode 100644 index 00000000..2d75a89a --- /dev/null +++ b/tests/lib/test_atomicalsconsensus_OP_NFT_PUT.py @@ -0,0 +1,141 @@ +import pytest + +from electrumx.lib.coins import Bitcoin +from electrumx.lib.hash import hex_str_to_hash + +from electrumx.lib.avm.util import ( + encode_int_value, + RequestInterpretParams, + RequestBlockchainContext, + print_result_states, + ScriptContext, + encode_op_pushdata +) + +from electrumx.lib.avm.avm import ( + RequestBlockchainContext, + RequestTxContext, + ReactorContext +) +from bitcointx.core.atomicalsconsensus import ( + ConsensusVerifyScriptAvmExecute, + AtomicalConsensusExecutionError +) + +from bitcointx.core.script import ( + CScript +) + +from cbor2 import dumps, loads + +coin = Bitcoin + +class MockLogger: + def debug(self, msg): + return + def info(self, msg): + return + def warning(self, msg): + return + +mock_current_header = '000000209174c9f2757e2647733c9ab69c133b90257f85ce7e9c0100000000000000000096e490f16d161416bef825bd4716b0914f344be5835cf6a8f0de89288a6c440391af0566d3620317aca8414a' +mock_headers = { + '840012': mock_current_header +} +mock_blockchain_context = RequestBlockchainContext(mock_headers, 840012) +mock_rawtx = bytes.fromhex('02000000018e469f953413e8d865fcf1f47d759772aa05e8d78b1e4577a58edc8bd09344ff010000006b483045022100ce16646785907c919a1658496a85cf3f3d877d98ffca5eabd189524acc1de53b02205ac24a9e2855db3da26b840c82fddaf73a5a6eff4b5b78cadfb00584ad6bd5f3012102cbcad7b21fb5fb08ad55eb09e327b97f63e8c5e99b2faf9bb330545a5bd4602cfeffffff0203761700000000001976a91496d02c013f734a642871261324c58091a806c23188ac9ce52300000000001976a914a9a8d5aa3ec73688d0540d45be3842f4603705c588ac6e640800') +mock_tx, mock_tx_hash = coin.DESERIALIZER(mock_rawtx, 0).read_tx_and_hash() +mock_empty_reactor_context = ReactorContext(None, dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) + +def test_atomicalsconsensus_OP_NFT_PUT_no_incoming_available_fail(): + with pytest.raises(AtomicalConsensusExecutionError) as exc: + payload = {} + balances = {} + sample_token_id1 = '012345678901234567890123456789012345678901234567890123456789000000000000' + sample_token_id1_bytes = bytearray.fromhex(sample_token_id1) + sample_token_id1_bytes.reverse() + sample_token_id1_encoded = encode_op_pushdata(sample_token_id1_bytes) + sample_token_id2 = '712345678901234567890123456789012345678901234567890123456789000000000002' + sample_token_id2_bytes = bytearray.fromhex(sample_token_id2) + sample_token_id2_bytes.reverse() + sample_token_id2_encoded = encode_op_pushdata(sample_token_id2_bytes) + balances[sample_token_id1] = True + state_hash = bytes.fromhex('0000000000000000000000000000000000000000000000000000000000000000') + request_tx_context = RequestTxContext(coin, mock_tx_hash, mock_tx, payload) + reactor_context = ReactorContext(state_hash, dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps(balances), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) + blockchain_context = RequestBlockchainContext(mock_headers, 840012) + script_context = ScriptContext(CScript(), CScript(bytes.fromhex(sample_token_id2_encoded.hex() + 'd1'))) + updated_reactor_state = ConsensusVerifyScriptAvmExecute(script_context, blockchain_context, request_tx_context, reactor_context) + assert updated_reactor_state + assert exc.value.error_code == 0 + assert exc.value.script_error == 82 + assert exc.value.script_error_op_num == 1 + +def test_atomicalsconsensus_OP_NFT_PUT_incoming_multiple_adds_fail(): + with pytest.raises(AtomicalConsensusExecutionError) as exc: + payload = {} + balances = {} + balances_incoming = {} + sample_token_id1 = '012345678901234567890123456789012345678901234567890123456789000000000000' + sample_token_id1_bytes = bytearray.fromhex(sample_token_id1) + sample_token_id1_bytes.reverse() + sample_token_id1_encoded = encode_op_pushdata(sample_token_id1_bytes) + sample_token_id2 = '712345678901234567890123456789012345678901234567890123456789000000000002' + sample_token_id2_bytes = bytearray.fromhex(sample_token_id2) + sample_token_id2_bytes.reverse() + sample_token_id2_encoded = encode_op_pushdata(sample_token_id2_bytes) + # balances[sample_token_id1] = True + balances_incoming[sample_token_id1] = True + state_hash = bytes.fromhex('0000000000000000000000000000000000000000000000000000000000000000') + request_tx_context = RequestTxContext(coin, mock_tx_hash, mock_tx, payload) + reactor_context = ReactorContext(state_hash, dumps({}), dumps({}), dumps({}), dumps(balances_incoming), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) + blockchain_context = RequestBlockchainContext(mock_headers, 840012) + script_context = ScriptContext(CScript(), CScript(bytes.fromhex(sample_token_id1_encoded.hex() + 'd1' + sample_token_id1_encoded.hex() + 'd1'))) + ConsensusVerifyScriptAvmExecute(script_context, blockchain_context, request_tx_context, reactor_context) + + assert exc.value.error_code == 0 + assert exc.value.script_error == 82 + assert exc.value.script_error_op_num == 3 + +def test_atomicalsconsensus_OP_NFT_PUT_incoming_new_success(): + payload = {} + balances = {} + balances_incoming = {} + sample_token_id1 = '012345678901234567890123456789012345678901234567890123456789000000000000' + sample_token_id1_bytes = bytearray.fromhex(sample_token_id1) + sample_token_id1_bytes.reverse() + sample_token_id1_encoded = encode_op_pushdata(sample_token_id1_bytes) + sample_token_id2 = '712345678901234567890123456789012345678901234567890123456789000000000002' + sample_token_id2_bytes = bytearray.fromhex(sample_token_id2) + sample_token_id2_bytes.reverse() + sample_token_id2_encoded = encode_op_pushdata(sample_token_id2_bytes) + balances_incoming[sample_token_id1] = True + balances_incoming[sample_token_id2] = True + state_hash = bytes.fromhex('0000000000000000000000000000000000000000000000000000000000000000') + request_tx_context = RequestTxContext(coin, mock_tx_hash, mock_tx, payload) + reactor_context = ReactorContext(state_hash, dumps({}), dumps({}), dumps({}), dumps(balances_incoming), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) + blockchain_context = RequestBlockchainContext(mock_headers, 840012) + script_context = ScriptContext(CScript(), CScript(bytes.fromhex(sample_token_id1_encoded.hex() + 'd151'))) + updated_reactor_state = ConsensusVerifyScriptAvmExecute(script_context, blockchain_context, request_tx_context, reactor_context) + + expected_balances = {} + expected_balances[sample_token_id1] = True + assert len(loads(updated_reactor_state.state)) == 0 + assert len(loads(updated_reactor_state.state_updates)) == 0 + assert len(loads(updated_reactor_state.state_deletes)) == 0 + assert len(loads(updated_reactor_state.ft_incoming)) == 0 + assert len(loads(updated_reactor_state.nft_incoming)) == 2 + assert len(loads(updated_reactor_state.ft_balances)) == 0 + expected_balances_updates = {} + expected_balances_updates[sample_token_id1] = True + assert len(loads(updated_reactor_state.ft_balances_updates)) == 0 + assert len(loads(updated_reactor_state.nft_balances)) == 1 + assert updated_reactor_state.nft_balances_updates == dumps(expected_balances_updates) + assert len(loads(updated_reactor_state.ft_withdraws)) == 0 + assert len(loads(updated_reactor_state.nft_withdraws)) == 0 + assert len(loads(updated_reactor_state.ft_adds)) == 0 + expected_nft_puts = {} + expected_nft_puts[sample_token_id1] = True + assert updated_reactor_state.nft_puts == dumps(expected_nft_puts) + assert len(loads(updated_reactor_state.nft_puts)) == 1 + \ No newline at end of file diff --git a/tests/lib/test_atomicalsconsensus_OP_NFT_WITHDRAW.py b/tests/lib/test_atomicalsconsensus_OP_NFT_WITHDRAW.py new file mode 100644 index 00000000..7d860611 --- /dev/null +++ b/tests/lib/test_atomicalsconsensus_OP_NFT_WITHDRAW.py @@ -0,0 +1,230 @@ +import pytest + +from electrumx.lib.coins import Bitcoin +from electrumx.lib.hash import hex_str_to_hash + +from electrumx.lib.avm.util import ( + encode_int_value, + RequestInterpretParams, + RequestBlockchainContext, + print_result_states, + encode_op_pushdata, + RequestTxContext, + ReactorContext, + ScriptContext +) +from electrumx.lib.avm.avm import ( + AVMFactory +) +from electrumx.lib.util_atomicals import ( + location_id_bytes_to_compact +) + +import bitcointx +import re +from electrumx.lib.util import ( + pack_le_uint64, + pack_le_uint32 +) +import struct + +from bitcointx.core.atomicalsconsensus import ( + ConsensusVerifyScriptAvmExecute, + AtomicalConsensusExecutionError +) + +from bitcointx.core.script import ( + CScript, CScriptOp, OP_1NEGATE +) +from bitcointx.core._bignum import ( + bn2vch +) + +from cbor2 import dumps, loads, CBORDecodeError + +coin = Bitcoin + +class MockLogger: + def debug(self, msg): + return + def info(self, msg): + return + def warning(self, msg): + return + +mock_current_header = '000000209174c9f2757e2647733c9ab69c133b90257f85ce7e9c0100000000000000000096e490f16d161416bef825bd4716b0914f344be5835cf6a8f0de89288a6c440391af0566d3620317aca8414a' +mock_headers = { + '840012': mock_current_header +} +mock_blockchain_context = RequestBlockchainContext(mock_headers, 840012) +mock_rawtx = bytes.fromhex('02000000018e469f953413e8d865fcf1f47d759772aa05e8d78b1e4577a58edc8bd09344ff010000006b483045022100ce16646785907c919a1658496a85cf3f3d877d98ffca5eabd189524acc1de53b02205ac24a9e2855db3da26b840c82fddaf73a5a6eff4b5b78cadfb00584ad6bd5f3012102cbcad7b21fb5fb08ad55eb09e327b97f63e8c5e99b2faf9bb330545a5bd4602cfeffffff0203761700000000001976a91496d02c013f734a642871261324c58091a806c23188ac9ce52300000000001976a914a9a8d5aa3ec73688d0540d45be3842f4603705c588ac6e640800') +mock_tx, mock_tx_hash = coin.DESERIALIZER(mock_rawtx, 0).read_tx_and_hash() +mock_empty_reactor_context = ReactorContext(None, dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) + +def test_atomicalsconsensus_OP_NFT_WITHDRAW_invalid_amount_for_no_id(): + with pytest.raises(AtomicalConsensusExecutionError) as exc: + payload = {} + balances = {} + sample_token_id1 = '012345678901234567890123456789012345678901234567890123456789000000000000' + sample_token_id1_bytes = bytearray.fromhex(sample_token_id1) + sample_token_id1_bytes.reverse() + sample_token_id1_encoded = encode_op_pushdata(sample_token_id1_bytes) + sample_token_id2 = '712345678901234567890123456789012345678901234567890123456789000000000002' + sample_token_id2_bytes = bytearray.fromhex(sample_token_id2) + sample_token_id2_bytes.reverse() + sample_token_id2_encoded = encode_op_pushdata(sample_token_id2_bytes) + balances[sample_token_id1] = True + state_hash = bytes.fromhex('0000000000000000000000000000000000000000000000000000000000000000') + request_tx_context = RequestTxContext(coin, mock_tx_hash, mock_tx, payload) + reactor_context = ReactorContext(state_hash, dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps(balances), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) + blockchain_context = RequestBlockchainContext(mock_headers, 840012) + script_context = ScriptContext(CScript(), CScript(bytes.fromhex(sample_token_id2_encoded.hex() + '00f3'))) + updated_reactor_state = ConsensusVerifyScriptAvmExecute(script_context, blockchain_context, request_tx_context, reactor_context) + assert updated_reactor_state + assert exc.value.error_code == 0 + assert exc.value.script_error == 65 + assert exc.value.script_error_op_num == 2 + +def test_atomicalsconsensus_OP_NFT_WITHDRAW_invalid_output_idx(): + with pytest.raises(AtomicalConsensusExecutionError) as exc: + payload = {} + balances = {} + sample_token_id1 = '012345678901234567890123456789012345678901234567890123456789000000000000' + sample_token_id1_bytes = bytearray.fromhex(sample_token_id1) + sample_token_id1_bytes.reverse() + sample_token_id1_encoded = encode_op_pushdata(sample_token_id1_bytes) + sample_token_id2 = '712345678901234567890123456789012345678901234567890123456789000000000002' + sample_token_id2_bytes = bytearray.fromhex(sample_token_id2) + sample_token_id2_bytes.reverse() + sample_token_id2_encoded = encode_op_pushdata(sample_token_id2_bytes) + balances[sample_token_id1] = True + state_hash = bytes.fromhex('0000000000000000000000000000000000000000000000000000000000000000') + request_tx_context = RequestTxContext(coin, mock_tx_hash, mock_tx, payload) + reactor_context = ReactorContext(state_hash, dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps(balances), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) + blockchain_context = RequestBlockchainContext(mock_headers, 840012) + script_context = ScriptContext(CScript(), CScript(bytes.fromhex(sample_token_id1_encoded.hex() + '55f3'))) + updated_reactor_state = ConsensusVerifyScriptAvmExecute(script_context, blockchain_context, request_tx_context, reactor_context) + assert updated_reactor_state + assert exc.value.error_code == 0 + assert exc.value.script_error == 66 + assert exc.value.script_error_op_num == 2 + +def test_atomicalsconsensus_OP_NFT_WITHDRAW_cannot_withdraw_more_than_once(): + with pytest.raises(AtomicalConsensusExecutionError) as exc: + payload = {} + balances = {} + sample_token_id1 = '012345678901234567890123456789012345678901234567890123456789000000000000' + sample_token_id1_bytes = bytearray.fromhex(sample_token_id1) + sample_token_id1_bytes.reverse() + sample_token_id1_encoded = encode_op_pushdata(sample_token_id1_bytes) + sample_token_id2 = '712345678901234567890123456789012345678901234567890123456789000000000002' + sample_token_id2_bytes = bytearray.fromhex(sample_token_id2) + sample_token_id2_bytes.reverse() + sample_token_id2_encoded = encode_op_pushdata(sample_token_id2_bytes) + balances[sample_token_id1] = True + state_hash = bytes.fromhex('0000000000000000000000000000000000000000000000000000000000000000') + request_tx_context = RequestTxContext(coin, mock_tx_hash, mock_tx, payload) + reactor_context = ReactorContext(state_hash, dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps(balances), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) + blockchain_context = RequestBlockchainContext(mock_headers, 840012) + script_context = ScriptContext(CScript(), CScript(bytes.fromhex(sample_token_id1_encoded.hex() + '00f3' + sample_token_id1_encoded.hex() + '00f3'))) + updated_reactor_state = ConsensusVerifyScriptAvmExecute(script_context, blockchain_context, request_tx_context, reactor_context) + assert updated_reactor_state + assert exc.value.error_code == 0 + assert exc.value.script_error == 65 + assert exc.value.script_error_op_num == 5 + +def test_atomicalsconsensus_OP_NFT_WITHDRAW_success_withdraw(): + payload = {} + balances = {} + sample_token_id1 = '012345678901234567890123456789012345678901234567890123456789000000000000' + sample_token_id1_bytes = bytearray.fromhex(sample_token_id1) + sample_token_id1_bytes.reverse() + sample_token_id1_encoded = encode_op_pushdata(sample_token_id1_bytes) + sample_token_id2 = '712345678901234567890123456789012345678901234567890123456789000000000002' + sample_token_id2_bytes = bytearray.fromhex(sample_token_id2) + sample_token_id2_bytes.reverse() + sample_token_id2_encoded = encode_op_pushdata(sample_token_id2_bytes) + balances[sample_token_id1] = True + balances[sample_token_id2] = True + state_hash = bytes.fromhex('0000000000000000000000000000000000000000000000000000000000000000') + request_tx_context = RequestTxContext(coin, mock_tx_hash, mock_tx, payload) + reactor_context = ReactorContext(state_hash, dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps(balances), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) + blockchain_context = RequestBlockchainContext(mock_headers, 840012) + script_context = ScriptContext(CScript(), CScript(bytes.fromhex(sample_token_id1_encoded.hex() + '00' + 'f351'))) + updated_reactor_state = ConsensusVerifyScriptAvmExecute(script_context, blockchain_context, request_tx_context, reactor_context) + assert updated_reactor_state + expected_balances = {} + # expected_balances[sample_token_id1] = 0 + expected_balances[sample_token_id2] = True + assert updated_reactor_state.nft_balances == dumps(expected_balances) + print(loads(updated_reactor_state.state)) + assert len(loads(updated_reactor_state.state)) == 0 + assert len(loads(updated_reactor_state.state_updates)) == 0 + assert len(loads(updated_reactor_state.state_deletes)) == 0 + assert len(loads(updated_reactor_state.ft_incoming)) == 0 + assert len(loads(updated_reactor_state.nft_incoming)) == 0 + assert len(loads(updated_reactor_state.ft_balances)) == 0 + assert len(loads(updated_reactor_state.ft_balances_updates)) == 0 + expected_balances = {} + expected_balances[sample_token_id2] = True + assert updated_reactor_state.nft_balances == dumps(expected_balances) + assert len(loads(updated_reactor_state.nft_balances)) == 1 + expected_nft_balances_updates = {} + expected_nft_balances_updates[sample_token_id1] = False + assert updated_reactor_state.nft_balances_updates == dumps(expected_nft_balances_updates) + expected_nft_withdraws = {} + expected_nft_withdraws[sample_token_id1] = 0 + print(loads(updated_reactor_state.nft_withdraws)) + assert updated_reactor_state.nft_withdraws == dumps(expected_nft_withdraws) + assert len(loads(updated_reactor_state.ft_withdraws)) == 0 + assert len(loads(updated_reactor_state.nft_withdraws)) == 1 + +def test_atomicalsconsensus_OP_NFT_WITHDRAW_incoming_success_withdraw(): + payload = {} + balances = {} + balances_incoming = {} + sample_token_id1 = '012345678901234567890123456789012345678901234567890123456789000000000000' + sample_token_id1_bytes = bytearray.fromhex(sample_token_id1) + sample_token_id1_bytes.reverse() + sample_token_id1_encoded = encode_op_pushdata(sample_token_id1_bytes) + sample_token_id2 = '712345678901234567890123456789012345678901234567890123456789000000000002' + sample_token_id2_bytes = bytearray.fromhex(sample_token_id2) + sample_token_id2_bytes.reverse() + sample_token_id2_encoded = encode_op_pushdata(sample_token_id2_bytes) + #balances[sample_token_id1] = True + balances[sample_token_id2] = True + balances_incoming[sample_token_id1] = True + state_hash = bytes.fromhex('0000000000000000000000000000000000000000000000000000000000000000') + request_tx_context = RequestTxContext(coin, mock_tx_hash, mock_tx, payload) + reactor_context = ReactorContext(state_hash, dumps({}), dumps({}), dumps({}), dumps(balances_incoming), dumps({}), dumps(balances), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) + blockchain_context = RequestBlockchainContext(mock_headers, 840012) + script_context = ScriptContext(CScript(), CScript(bytes.fromhex(sample_token_id1_encoded.hex() + 'd1' + sample_token_id1_encoded.hex() + '00' + 'f351'))) + updated_reactor_state = ConsensusVerifyScriptAvmExecute(script_context, blockchain_context, request_tx_context, reactor_context) + assert updated_reactor_state + expected_balances = {} + # expected_balances[sample_token_id1] = 0 + expected_balances[sample_token_id2] = True + assert updated_reactor_state.nft_balances == dumps(expected_balances) + print(loads(updated_reactor_state.state)) + assert len(loads(updated_reactor_state.state)) == 0 + assert len(loads(updated_reactor_state.state_updates)) == 0 + assert len(loads(updated_reactor_state.state_deletes)) == 0 + assert len(loads(updated_reactor_state.ft_incoming)) == 0 + assert len(loads(updated_reactor_state.nft_incoming)) == 1 + assert len(loads(updated_reactor_state.ft_balances)) == 0 + assert len(loads(updated_reactor_state.ft_balances_updates)) == 0 + expected_balances = {} + expected_balances[sample_token_id2] = True + assert updated_reactor_state.nft_balances == dumps(expected_balances) + assert len(loads(updated_reactor_state.nft_balances)) == 1 + expected_nft_balances_updates = {} + expected_nft_balances_updates[sample_token_id1] = False + assert updated_reactor_state.nft_balances_updates == dumps(expected_nft_balances_updates) + expected_nft_withdraws = {} + expected_nft_withdraws[sample_token_id1] = 0 + print(loads(updated_reactor_state.nft_withdraws)) + assert updated_reactor_state.nft_withdraws == dumps(expected_nft_withdraws) + assert len(loads(updated_reactor_state.ft_withdraws)) == 0 + assert len(loads(updated_reactor_state.nft_withdraws)) == 1 + + \ No newline at end of file diff --git a/tests/lib/test_avm.py b/tests/lib/test_avm.py new file mode 100644 index 00000000..7e316b3d --- /dev/null +++ b/tests/lib/test_avm.py @@ -0,0 +1,166 @@ +import pytest + +from electrumx.lib.coins import Bitcoin +from electrumx.lib.hash import hex_str_to_hash + +from electrumx.lib.avm.avm import ( + AVMFactory, + RequestBlockchainContext, + RequestTxContext, + ReactorContext +) +from cbor2 import dumps, loads +coin = Bitcoin + +class MockLogger: + def debug(self, msg): + return + def info(self, msg): + return + def warning(self, msg): + return + +def mock_mint_fetcher(atomical_id): + return { + } + +mock_current_header = '000000209174c9f2757e2647733c9ab69c133b90257f85ce7e9c0100000000000000000096e490f16d161416bef825bd4716b0914f344be5835cf6a8f0de89288a6c440391af0566d3620317aca8414a' +mock_headers = { + '840012': mock_current_header +} +mock_blockchain_context = RequestBlockchainContext(mock_headers, 840012) +mock_rawtx = bytes.fromhex('02000000018e469f953413e8d865fcf1f47d759772aa05e8d78b1e4577a58edc8bd09344ff010000006b483045022100ce16646785907c919a1658496a85cf3f3d877d98ffca5eabd189524acc1de53b02205ac24a9e2855db3da26b840c82fddaf73a5a6eff4b5b78cadfb00584ad6bd5f3012102cbcad7b21fb5fb08ad55eb09e327b97f63e8c5e99b2faf9bb330545a5bd4602cfeffffff0203761700000000001976a91496d02c013f734a642871261324c58091a806c23188ac9ce52300000000001976a914a9a8d5aa3ec73688d0540d45be3842f4603705c588ac6e640800') +mock_tx, mock_tx_hash = coin.DESERIALIZER(mock_rawtx, 0).read_tx_and_hash() +mock_empty_reactor_context = ReactorContext(None, dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({}), dumps({})) + +def test_execute_deploy_script1(): + protocol_mint_data = { + 'p': 'ppp', + 'code': bytes.fromhex('5187'), + 'fn': [ + { + 'name': 'ctor', + 'params': [ + { + 'name': 'age', + 'type': 'int' + } + ] + } + ] + } + avm = AVMFactory(MockLogger(), mock_mint_fetcher, mock_blockchain_context, protocol_mint_data) + payload = { + 'p': 'ppp', + 'u': bytes.fromhex('51'), + 'args': { + 'age': 1 + } + } + atomicals_spent_at_inputs = {} + request_tx_context = RequestTxContext(coin, mock_tx_hash, mock_tx, payload) + + deploy_command = avm.create_deploy_command(request_tx_context, atomicals_spent_at_inputs, mock_empty_reactor_context) + assert(deploy_command.is_valid) + assert(deploy_command.unlock_script.hex() == '51') + assert(deploy_command.lock_script.hex() == '5187') + + result = deploy_command.execute() + assert result.success + assert result.reactor_context + + result_context = result.reactor_context + state_hash = result_context.state_hash + state = loads(result_context.state) + state_updates = loads(result_context.state_updates) + state_deletes = loads(result_context.state_deletes) + ft_incoming = loads(result_context.ft_incoming) + nft_incoming = loads(result_context.nft_incoming) + ft_balances = loads(result_context.ft_balances) + ft_balances_updates = loads(result_context.ft_balances_updates) + nft_balances = loads(result_context.nft_balances) + nft_balances_updates = loads(result_context.nft_balances_updates) + ft_withdraws = loads(result_context.ft_withdraws) + nft_withdraws = loads(result_context.nft_withdraws) + + assert state_hash.hex() == '71daaf262004b5778dfb085daf074cf22a9e4c6f60eb8700974ba6bd3cc2b156' + assert len(state) == 0 + assert len(state_updates) == 0 + assert len(state_deletes) == 0 + assert len(ft_incoming) == 0 + assert len(nft_incoming) == 0 + assert len(ft_balances) == 0 + assert len(ft_balances_updates) == 0 + assert len(nft_balances) == 0 + assert len(nft_balances_updates) == 0 + assert len(ft_withdraws) == 0 + assert len(nft_withdraws) == 0 + +mock_rawtx2 = bytes.fromhex('0100000000010156d0f907b0a3385095afb426f46762f4305f39c30b626753b0365c51307f6bac0000000000ffffffff01e803000000000000225120c2529c35bacd75646eb3135ca1270325ba2bab5d473b7c8a1eab54b21deea86e034090e7d4e121a12d1ec63820af4d59663a54c31ca4da6dd861257b728a69c8f61cfad799fce6fc6388eba63e02eca4a3c7df82fa055cd6c2e5c771749c71744a0474209f76f4e90e426cae195e7092cf8ec81d3005a90574e6d0532c679983ad79d7eaac00630461746f6d036e657745a46170667365636f6e64626f70666465706c6f796461726773a36474696d651a66845c51656e6f6e63650068626974776f726b636161646e616d6569636f6e7472616374316821c09f76f4e90e426cae195e7092cf8ec81d3005a90574e6d0532c679983ad79d7ea00000000') +mock_tx2, mock_tx_hash2 = coin.DESERIALIZER(mock_rawtx2, 0).read_tx_and_hash() + +def test_execute_deploy_script2(): + print(f'mock: {mock_tx_hash2.hex()}') + protocol_mint_data = { + 'p': 'ppp', + 'code': bytes.fromhex('51'), + 'fn': [ + { + 'name': 'ctor', + 'params': [ + ] + } + ] + } + avm = AVMFactory(MockLogger(), mock_mint_fetcher, mock_blockchain_context, protocol_mint_data) + payload = { + 'op': 'deploy', + 'p': 'ppp', + 'args': { + } + } + atomicals_spent_at_inputs = {} + request_tx_context = RequestTxContext(coin, mock_tx_hash2, mock_tx2, payload) + + deploy_command = avm.create_deploy_command(request_tx_context, atomicals_spent_at_inputs, mock_empty_reactor_context) + assert(deploy_command.is_valid) + assert(deploy_command.unlock_script.hex() == '') + assert(deploy_command.lock_script.hex() == '51') + + result = deploy_command.execute() + assert result.success + assert result.reactor_context + + result_context = result.reactor_context + state_hash = result_context.state_hash + state = loads(result_context.state) + state_updates = loads(result_context.state_updates) + state_deletes = loads(result_context.state_deletes) + ft_incoming = loads(result_context.ft_incoming) + nft_incoming = loads(result_context.nft_incoming) + ft_balances = loads(result_context.ft_balances) + ft_balances_updates = loads(result_context.ft_balances_updates) + nft_balances = loads(result_context.nft_balances) + nft_balances_updates = loads(result_context.nft_balances_updates) + ft_withdraws = loads(result_context.ft_withdraws) + nft_withdraws = loads(result_context.nft_withdraws) + ft_adds = loads(result_context.ft_adds) + nft_puts = loads(result_context.nft_puts) + + assert state_hash.hex() == '71daaf262004b5778dfb085daf074cf22a9e4c6f60eb8700974ba6bd3cc2b156' + assert len(state) == 0 + assert len(state_updates) == 0 + assert len(state_deletes) == 0 + assert len(ft_incoming) == 0 + assert len(nft_incoming) == 0 + assert len(ft_balances) == 0 + assert len(ft_balances_updates) == 0 + assert len(nft_balances) == 0 + assert len(nft_balances_updates) == 0 + assert len(ft_withdraws) == 0 + assert len(nft_withdraws) == 0 + assert len(nft_puts) == 0 + assert len(ft_adds) == 0 + + +