Skip to content

Commit

Permalink
Merge pull request #233 from atomicals/fix/transactions-decode
Browse files Browse the repository at this point in the history
  • Loading branch information
wizz-wallet-dev authored Nov 21, 2024
2 parents a484e00 + 7b77a9c commit 8060bb1
Show file tree
Hide file tree
Showing 7 changed files with 101 additions and 76 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,5 @@ docs/_build
.idea/
.env
.venv
*.log
*.http
8 changes: 8 additions & 0 deletions electrumx/lib/util_atomicals.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand All @@ -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):
Expand Down
2 changes: 1 addition & 1 deletion electrumx/server/block_processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions electrumx/server/session/http_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
29 changes: 19 additions & 10 deletions electrumx/server/session/session_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
NewlineFramer,
ReplyAndDisconnect,
Request,
RPCError,
RPCSession,
handler_invocation,
)
Expand Down Expand Up @@ -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
Expand All @@ -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))
124 changes: 65 additions & 59 deletions electrumx/server/session/session_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 = {
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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():
Expand Down Expand Up @@ -1146,6 +1090,8 @@ def make_transfer_outputs(
result[k].append(_data)
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:
Expand All @@ -1158,6 +1104,66 @@ def make_transfer_outputs(
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 {}
res["info"]["payload"] = payload_not_none
# 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)
# 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"])
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({
"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,
})
if len(receives) > 0:
outputs[expected_output_index] = receives
res["info"] = {
"payload": payload,
"outputs": outputs,
}

(
payment_id,
payment_marker_idx,
Expand Down
8 changes: 4 additions & 4 deletions electrumx/server/session/shared_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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):
Expand Down

0 comments on commit 8060bb1

Please sign in to comment.