From a46484f1f8a4b5ea8d1d3c33f56c40e235e0c3bf Mon Sep 17 00:00:00 2001 From: Wizz Wallet <153743376+wizz-wallet-dev@users.noreply.github.com> Date: Sat, 5 Oct 2024 19:14:37 +0800 Subject: [PATCH 1/9] Fixes typos --- electrumx/server/block_processor.py | 2 +- electrumx/server/session/session_manager.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/electrumx/server/block_processor.py b/electrumx/server/block_processor.py index b0e93432..55f3e8ae 100644 --- a/electrumx/server/block_processor.py +++ b/electrumx/server/block_processor.py @@ -3529,7 +3529,7 @@ def build_atomicals_spent_at_inputs_for_validation_only(self, tx): # Builds a map of the atomicals spent at a tx # It uses the spend_atomicals_utxo method but with live_run == False - def build_atomicals_receive_at_ouutput_for_validation_only(self, tx, txid): + def build_atomicals_receive_at_output_for_validation_only(self, tx, txid): spend_atomicals_utxo = self.spend_atomicals_utxo atomicals_receive_at_outputs = {} txout_index = 0 diff --git a/electrumx/server/session/session_manager.py b/electrumx/server/session/session_manager.py index 194a6236..23a1799a 100644 --- a/electrumx/server/session/session_manager.py +++ b/electrumx/server/session/session_manager.py @@ -953,7 +953,7 @@ async def transaction_decode_raw_tx_blueprint( }, } elif op == "nft": - _receive_at_outputs = self.bp.build_atomicals_receive_at_ouutput_for_validation_only(tx, tx_hash) + _receive_at_outputs = self.bp.build_atomicals_receive_at_output_for_validation_only(tx, tx_hash) tx_out = tx.outputs[0] atomical_id = location_id_bytes_to_compact(_receive_at_outputs[0][-1]["atomical_id"]) mint_info = { @@ -1011,7 +1011,7 @@ async def get_transaction_detail(self, tx_id: str, height=None, tx_num=-1): operation_found_at_inputs = parse_protocols_operations_from_witness_array(tx, tx_hash, True) atomicals_spent_at_inputs = self.bp.build_atomicals_spent_at_inputs_for_validation_only(tx) - atomicals_receive_at_outputs = self.bp.build_atomicals_receive_at_ouutput_for_validation_only(tx, tx_hash) + atomicals_receive_at_outputs = self.bp.build_atomicals_receive_at_output_for_validation_only(tx, tx_hash) blueprint_builder = AtomicalsTransferBlueprintBuilder( self.logger, atomicals_spent_at_inputs, From 81b64b1094e4d4dd44edf6749852509c94f30486 Mon Sep 17 00:00:00 2001 From: Wizz Wallet <153743376+wizz-wallet-dev@users.noreply.github.com> Date: Sat, 5 Oct 2024 19:15:05 +0800 Subject: [PATCH 2/9] Decodes all NFT Atomicals outputs with the transaction --- electrumx/server/session/session_manager.py | 112 ++++++++++---------- 1 file changed, 55 insertions(+), 57 deletions(-) diff --git a/electrumx/server/session/session_manager.py b/electrumx/server/session/session_manager.py index 23a1799a..7c462806 100644 --- a/electrumx/server/session/session_manager.py +++ b/electrumx/server/session/session_manager.py @@ -7,7 +7,7 @@ from collections import defaultdict from functools import partial from ipaddress import IPv4Address, IPv4Network, IPv6Address, IPv6Network -from typing import TYPE_CHECKING, Dict, List, Optional +from typing import TYPE_CHECKING, Any, Dict, List, Optional import attr import pylru @@ -1043,62 +1043,6 @@ async def get_transaction_detail(self, tx_id: str, height=None, tx_num=-1): "is_cleanly_assigned": is_cleanly_assigned, }, } - operation_type = operation_found_at_inputs.get("op", "") if operation_found_at_inputs else "" - if operation_found_at_inputs: - payload = operation_found_at_inputs.get("payload") - payload_not_none = payload or {} - res["info"]["payload"] = payload_not_none - if blueprint_builder.is_mint and operation_type in ["dmt", "ft"]: - expected_output_index = 0 - tx_out = tx.outputs[expected_output_index] - location = tx_hash + util.pack_le_uint32(expected_output_index) - # if save into the db, it means mint success - has_atomicals = self.db.get_atomicals_by_location_long_form(location) - if len(has_atomicals): - ticker_name = payload_not_none.get("args", {}).get("mint_ticker", "") - status, candidate_atomical_id, _ = self.bp.get_effective_ticker(ticker_name, self.bp.height) - if status: - atomical_id = location_id_bytes_to_compact(candidate_atomical_id) - res["info"] = { - "atomical_id": atomical_id, - "location_id": location_id_bytes_to_compact(location), - "payload": payload, - "outputs": { - expected_output_index: [ - { - "address": get_address_from_output_script(tx_out.pk_script), - "atomical_id": atomical_id, - "type": "FT", - "index": expected_output_index, - "value": tx_out.value, - } - ] - }, - } - elif operation_type == "nft": - if atomicals_receive_at_outputs: - expected_output_index = 0 - location = tx_hash + util.pack_le_uint32(expected_output_index) - tx_out = tx.outputs[expected_output_index] - atomical_id = location_id_bytes_to_compact( - atomicals_receive_at_outputs[expected_output_index][-1]["atomical_id"] - ) - res["info"] = { - "atomical_id": atomical_id, - "location_id": location_id_bytes_to_compact(location), - "payload": payload, - "outputs": { - expected_output_index: [ - { - "address": get_address_from_output_script(tx_out.pk_script), - "atomical_id": atomical_id, - "type": "NFT", - "index": expected_output_index, - "value": tx_out.value, - } - ] - }, - } async def make_transfer_inputs(result, inputs_atomicals, tx_inputs, make_type) -> Dict[int, List[Dict]]: for atomical_id, input_data in inputs_atomicals.items(): @@ -1146,6 +1090,60 @@ def make_transfer_outputs( result[k].append(_data) return result + operation_type = operation_found_at_inputs.get("op", "") if operation_found_at_inputs else "" + if operation_found_at_inputs: + payload = operation_found_at_inputs.get("payload") + payload_not_none = payload or {} + res["info"]["payload"] = payload_not_none + if blueprint_builder.is_mint and operation_type in ["dmt", "ft"]: + expected_output_index = 0 + tx_out = tx.outputs[expected_output_index] + location = tx_hash + util.pack_le_uint32(expected_output_index) + # if save into the db, it means mint success + has_atomicals = self.db.get_atomicals_by_location_long_form(location) + if len(has_atomicals): + ticker_name = payload_not_none.get("args", {}).get("mint_ticker", "") + status, candidate_atomical_id, _ = self.bp.get_effective_ticker(ticker_name, self.bp.height) + if status: + atomical_id = location_id_bytes_to_compact(candidate_atomical_id) + res["info"] = { + "payload": payload, + "outputs": { + expected_output_index: [ + { + "address": get_address_from_output_script(tx_out.pk_script), + "atomical_id": atomical_id, + "location_id": location_id_bytes_to_compact(location), + "type": "FT", + "index": expected_output_index, + "value": tx_out.value, + } + ] + }, + } + elif operation_type == "nft": + if atomicals_receive_at_outputs: + outputs: Dict[int, List[Dict[str, Any]]] = {} + for expected_output_index, atomicals_receives in atomicals_receive_at_outputs.items(): + receives: List[Dict[str, Any]] = [] + for atomicals in atomicals_receives: + atomical_id = location_id_bytes_to_compact(atomicals["atomical_id"]) + location = tx_hash + util.pack_le_uint32(expected_output_index) + tx_out = tx.outputs[expected_output_index] + receives.append({ + "address": get_address_from_output_script(tx_out.pk_script), + "atomical_id": atomical_id, + "location_id": location_id_bytes_to_compact(location), + "type": "NFT", + "index": expected_output_index, + "value": tx_out.value, + }) + outputs[expected_output_index] = receives + res["info"] = { + "payload": payload, + "outputs": outputs, + } + # no operation_found_at_inputs, it will be transfer. if blueprint_builder.ft_atomicals and atomicals_spent_at_inputs: if not operation_type and not op_raw: From 5765b328325410040065209bbe5f7d4c600f043b Mon Sep 17 00:00:00 2001 From: Wizz Wallet <153743376+wizz-wallet-dev@users.noreply.github.com> Date: Sat, 5 Oct 2024 20:33:48 +0800 Subject: [PATCH 3/9] Exclude mint outputs if exists in transfers --- electrumx/server/session/session_manager.py | 30 ++++++++++++--------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/electrumx/server/session/session_manager.py b/electrumx/server/session/session_manager.py index 7c462806..e2e13b6b 100644 --- a/electrumx/server/session/session_manager.py +++ b/electrumx/server/session/session_manager.py @@ -1091,6 +1091,19 @@ def make_transfer_outputs( return result operation_type = operation_found_at_inputs.get("op", "") if operation_found_at_inputs else "" + + # no operation_found_at_inputs, it will be transfer. + if blueprint_builder.ft_atomicals and atomicals_spent_at_inputs: + if not operation_type and not op_raw: + op_raw = "transfer" + await make_transfer_inputs(res["transfers"]["inputs"], blueprint_builder.ft_atomicals, tx.inputs, "FT") + make_transfer_outputs(res["transfers"]["outputs"], blueprint_builder.ft_output_blueprint.outputs) + if blueprint_builder.nft_atomicals and atomicals_spent_at_inputs: + if not operation_type and not op_raw: + op_raw = "transfer" + await make_transfer_inputs(res["transfers"]["inputs"], blueprint_builder.nft_atomicals, tx.inputs, "NFT") + make_transfer_outputs(res["transfers"]["outputs"], blueprint_builder.nft_output_blueprint.outputs) + if operation_found_at_inputs: payload = operation_found_at_inputs.get("payload") payload_not_none = payload or {} @@ -1128,6 +1141,11 @@ def make_transfer_outputs( receives: List[Dict[str, Any]] = [] for atomicals in atomicals_receives: atomical_id = location_id_bytes_to_compact(atomicals["atomical_id"]) + if any( + any(output.get("atomical_id") == atomical_id for output in outputs_list) + for outputs_list in res["transfers"]["outputs"].values() + ): + continue location = tx_hash + util.pack_le_uint32(expected_output_index) tx_out = tx.outputs[expected_output_index] receives.append({ @@ -1144,18 +1162,6 @@ def make_transfer_outputs( "outputs": outputs, } - # no operation_found_at_inputs, it will be transfer. - if blueprint_builder.ft_atomicals and atomicals_spent_at_inputs: - if not operation_type and not op_raw: - op_raw = "transfer" - await make_transfer_inputs(res["transfers"]["inputs"], blueprint_builder.ft_atomicals, tx.inputs, "FT") - make_transfer_outputs(res["transfers"]["outputs"], blueprint_builder.ft_output_blueprint.outputs) - if blueprint_builder.nft_atomicals and atomicals_spent_at_inputs: - if not operation_type and not op_raw: - op_raw = "transfer" - await make_transfer_inputs(res["transfers"]["inputs"], blueprint_builder.nft_atomicals, tx.inputs, "NFT") - make_transfer_outputs(res["transfers"]["outputs"], blueprint_builder.nft_output_blueprint.outputs) - ( payment_id, payment_marker_idx, From d8b64f6fa8e842268bb1e5c1399c63c3b976528a Mon Sep 17 00:00:00 2001 From: Wizz Wallet <153743376+wizz-wallet-dev@users.noreply.github.com> Date: Sat, 5 Oct 2024 20:39:10 +0800 Subject: [PATCH 4/9] Improve the predication --- electrumx/server/session/session_manager.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/electrumx/server/session/session_manager.py b/electrumx/server/session/session_manager.py index e2e13b6b..2accd95d 100644 --- a/electrumx/server/session/session_manager.py +++ b/electrumx/server/session/session_manager.py @@ -1108,7 +1108,8 @@ def make_transfer_outputs( payload = operation_found_at_inputs.get("payload") payload_not_none = payload or {} res["info"]["payload"] = payload_not_none - if blueprint_builder.is_mint and operation_type in ["dmt", "ft"]: + # Mint operation types are "dmt", "nft", "ft", "dft". "dft" is the deploy operation. + if operation_type in ["dmt", "ft"]: expected_output_index = 0 tx_out = tx.outputs[expected_output_index] location = tx_hash + util.pack_le_uint32(expected_output_index) From 404b7b9e379fef5dacaa4b85a9e5c2a27f2eabad Mon Sep 17 00:00:00 2001 From: Wizz Wallet <153743376+wizz-wallet-dev@users.noreply.github.com> Date: Sun, 6 Oct 2024 15:16:10 +0800 Subject: [PATCH 5/9] Put records only when not empty --- electrumx/server/session/session_manager.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/electrumx/server/session/session_manager.py b/electrumx/server/session/session_manager.py index 2accd95d..aa286155 100644 --- a/electrumx/server/session/session_manager.py +++ b/electrumx/server/session/session_manager.py @@ -1157,7 +1157,8 @@ def make_transfer_outputs( "index": expected_output_index, "value": tx_out.value, }) - outputs[expected_output_index] = receives + if len(receives) > 0: + outputs[expected_output_index] = receives res["info"] = { "payload": payload, "outputs": outputs, From 6477393f0ada006b323d5289727773f883d4d8a2 Mon Sep 17 00:00:00 2001 From: Wizz Wallet <153743376+wizz-wallet-dev@users.noreply.github.com> Date: Wed, 23 Oct 2024 08:10:55 +0800 Subject: [PATCH 6/9] Handles unknown undefined type --- electrumx/lib/util_atomicals.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/electrumx/lib/util_atomicals.py b/electrumx/lib/util_atomicals.py index 7d98bed0..6959283f 100644 --- a/electrumx/lib/util_atomicals.py +++ b/electrumx/lib/util_atomicals.py @@ -1450,6 +1450,10 @@ def auto_encode_bytes_elements(state): for key, value in state.items(): state[key] = auto_encode_bytes_elements(value) + # Handles unknown undefined type. + if type(state).__name__ == 'undefined_type': + return None + return state @@ -1468,6 +1472,10 @@ def auto_encode_bytes_items(state): reformatted_list.append(auto_encode_bytes_elements(item)) return reformatted_list + # Handles unknown undefined type. + if type(state).__name__ == 'undefined_type': + return None + cloned_state = {} try: if isinstance(state, dict): From 6871e4ebef9c5ccbb50cceb7560c098d7b2407c7 Mon Sep 17 00:00:00 2001 From: Wizz Wallet <153743376+wizz-wallet-dev@users.noreply.github.com> Date: Sat, 26 Oct 2024 17:08:57 +0800 Subject: [PATCH 7/9] Wraps session base handler with RPCError results --- electrumx/server/session/session_base.py | 29 ++++++++++++++++-------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/electrumx/server/session/session_base.py b/electrumx/server/session/session_base.py index 5991c805..df45635e 100644 --- a/electrumx/server/session/session_base.py +++ b/electrumx/server/session/session_base.py @@ -7,6 +7,7 @@ NewlineFramer, ReplyAndDisconnect, Request, + RPCError, RPCSession, handler_invocation, ) @@ -119,14 +120,17 @@ async def handle_request(self, request): """Handle an incoming request. ElectrumX doesn't receive notifications from client sessions. """ + method = request.method if isinstance(request, Request): - handler = self.request_handlers.get(request.method) - method = request.method - args = request.args + handler = self.request_handlers.get(method) else: handler = None - method = "invalid method" - args = None + if handler is None: + from aiorpcx import JSONRPC + self.logger.error(f'Unknown handler for the method "{method}"') + return RPCError(JSONRPC.METHOD_NOT_FOUND, f'Unknown handler for the method "{method}"') + + args = request.args self.logger.debug(f"Session request handling: [method] {method}, [args] {args}") # If DROP_CLIENT_UNKNOWN is enabled, check if the client identified @@ -136,8 +140,13 @@ async def handle_request(self, request): raise ReplyAndDisconnect(BAD_REQUEST, "use server.version to identify client") self.session_mgr.method_counts[method] += 1 - coro = handler_invocation(handler, request)() - if isinstance(coro, Awaitable): - return await coro - else: - return coro + + # Wraps all internal errors without closing the session. + try: + result = handler_invocation(handler, request)() + if isinstance(result, Awaitable): + result = await result + return result + except BaseException as e: + self.logger.error(f"Session request error: [method] {method}, [args] {args}, [error] {e}") + return RPCError(-1, str(e)) From 8b7d5d8d4afd7d47f43c1029866592f21b807b39 Mon Sep 17 00:00:00 2001 From: Wizz Wallet <153743376+wizz-wallet-dev@users.noreply.github.com> Date: Sat, 26 Oct 2024 17:09:29 +0800 Subject: [PATCH 8/9] Remove unnecessary asynchronous modifiers --- electrumx/server/session/http_session.py | 4 ++-- electrumx/server/session/shared_session.py | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/electrumx/server/session/http_session.py b/electrumx/server/session/http_session.py index c523d1c6..c05a488c 100644 --- a/electrumx/server/session/http_session.py +++ b/electrumx/server/session/http_session.py @@ -241,10 +241,10 @@ async def donation_address(self): """Return the donation address as a string, empty if there is none.""" return self.env.donation_address - async def server_features_async(self): + def server_features_async(self): return self.server_features(self.env) - async def peers_subscribe(self): + def peers_subscribe(self): """Return the server peers as a list of (ip, host, details) tuples.""" return self.peer_mgr.on_peers_subscribe(False) diff --git a/electrumx/server/session/shared_session.py b/electrumx/server/session/shared_session.py index e52da727..996aeaa4 100644 --- a/electrumx/server/session/shared_session.py +++ b/electrumx/server/session/shared_session.py @@ -771,21 +771,21 @@ async def atomicals_get_container_items(self, container, limit, offset): } } - async def atomicals_search_tickers(self, prefix=None, reverse=False, limit=100, offset=0, is_verified_only=False): + def atomicals_search_tickers(self, prefix=None, reverse=False, limit=100, offset=0, is_verified_only=False): if isinstance(prefix, str): prefix = prefix.encode() return self._atomicals_search_name_template( b"tick", "ticker", None, prefix, reverse, limit, offset, is_verified_only ) - async def atomicals_search_realms(self, prefix=None, reverse=False, limit=100, offset=0, is_verified_only=False): + def atomicals_search_realms(self, prefix=None, reverse=False, limit=100, offset=0, is_verified_only=False): if isinstance(prefix, str): prefix = prefix.encode() return self._atomicals_search_name_template( b"rlm", "realm", None, prefix, reverse, limit, offset, is_verified_only ) - async def atomicals_search_subrealms( + def atomicals_search_subrealms( self, parent, prefix=None, @@ -808,7 +808,7 @@ async def atomicals_search_subrealms( is_verified_only, ) - async def atomicals_search_containers( + def atomicals_search_containers( self, prefix=None, reverse=False, limit=100, offset=0, is_verified_only=False ): if isinstance(prefix, str): From 7b77a9c95cc1529461fcf2a9a8bd5bdebf31fab1 Mon Sep 17 00:00:00 2001 From: Wizz Wallet <153743376+wizz-wallet-dev@users.noreply.github.com> Date: Mon, 4 Nov 2024 10:07:21 +0800 Subject: [PATCH 9/9] Ignore more files --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 61731016..df20f122 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,5 @@ docs/_build .idea/ .env .venv +*.log +*.http