Skip to content

Commit

Permalink
🐛 Fix payments recognition during advances (#180)
Browse files Browse the repository at this point in the history
The verified status of an Atomical relies on the reveal window. The height in the query is the latest advanced block height rather than the processing height, which leads to an incorrect verified status for the container and realm.

The issue used to produce incorrect block hashes, probably incorrect Atomicals assets.

See height:`tx` records below for more details on the testnet:
- 2532818: `b756276cdd06cc188c0266010c9fbf3e416089967a898275ca2909b93081f3ce`
- 2569512: `a98015464f0859b5f56011b4c0fb8911a83cac61cd77730744447919c79471a0`

Verified with random start heights reindexing on the testnet.

### Before
- 2532818: `2973a121b4886ec01ad5676e10e00481edf7662fe5027e1e56704b7f31025050`
- 2569512: `45a26b5c0370167df19e0dabb6e57c1e4b631d9812d82832b8c05158447b1942`

### Expected:
- 2532818: `918917675000438163934ef4dc882e0883c3fdc2dca3346c178f83e6c7e2ecd2`
- 2569512: `dfc1367fc7445a2feb9a39e875cd6f142dabbf27bb63085425bd8a35f738456c`
  • Loading branch information
AlexV525 authored May 11, 2024
1 parent 47fd473 commit 4e530ac
Showing 1 changed file with 29 additions and 31 deletions.
60 changes: 29 additions & 31 deletions electrumx/server/block_processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -658,7 +658,7 @@ def get_expected_subrealm_payment_info(self, found_atomical_id_for_potential_sub
assert(request_parent_realm_id_compact == parent_realm_id_compact)
if isinstance(parent_realm_id_compact, str) and is_compact_atomical_id(parent_realm_id_compact):
# We have a validated potential parent id, now look it up to see if the parent is a valid atomical
found_parent_mint_info = self.get_base_mint_info_by_atomical_id(parent_realm_id)
found_parent_mint_info = self.get_base_mint_info_by_atomical_id(parent_realm_id, height=current_height)
if found_parent_mint_info:
# We have found the parent atomical, which may or may not be a valid realm
# Do the basic check for $request_realm which indicates it succeeded the basic validity checks
Expand Down Expand Up @@ -747,8 +747,7 @@ def get_expected_dmitem_payment_info(self, found_atomical_id_for_potential_dmite
self.logger.info(f'get_expected_dmitem_payment_info: parent_container_id_compact not string or compact atomical id {location_id_bytes_to_compact(found_atomical_id_for_potential_dmitem)} parent_container_id_compact={parent_container_id_compact}')
return None, None, None, None
# We have a validated potential parent id, now look it up to see if the parent is a valid atomical
# found_parent_mint_info = self.get_base_mint_info_by_atomical_id(parent_container_id)
found_parent_mint_info = self.get_base_mint_info_by_atomical_id(parent_container_id)
found_parent_mint_info = self.get_base_mint_info_by_atomical_id(parent_container_id, height=current_height)
if not found_parent_mint_info:
self.logger.info(f'get_expected_dmitem_payment_info: not found_parent_mint_info found_atomical_id_for_potential_dmitem={location_id_bytes_to_compact(found_atomical_id_for_potential_dmitem)} parent_container_id_compact={parent_container_id_compact} found_atomical_mint_info_for_potential_dmitem={found_atomical_mint_info_for_potential_dmitem}')
return None, None, None, None
Expand Down Expand Up @@ -2209,7 +2208,8 @@ def get_raw_mint_info_by_atomical_id_notused(self, atomical_id):
# Get the atomical details base info
# Does not retrieve the active b'a' locations in this method because there could be many thousands (in the case of FTs)
# Another method is provided to layer on the active location and gives the user control over whether to retrieve them
def get_base_mint_info_by_atomical_id(self, atomical_id):
def get_base_mint_info_by_atomical_id(self, atomical_id, height: Optional[int] = None):
height = height if height else self.height
init_mint_info = self.get_atomicals_id_mint_info(atomical_id, True)
if not init_mint_info:
return None
Expand Down Expand Up @@ -2329,9 +2329,9 @@ def get_base_mint_info_by_atomical_id(self, atomical_id):
atomical['$parents'] = parents

# Resolve any name like details such as realms, subrealms, containers and tickers
self.populate_extended_atomical_subtype_info(atomical)
self.populate_extended_atomical_subtype_info(atomical, height)
self.populate_sealed_status(atomical)
self.populate_container_dmint_status(atomical)
self.populate_container_dmint_status(atomical, height)

return atomical

Expand All @@ -2349,10 +2349,10 @@ def populate_sealed_status(self, atomical):
atomical['$sealed'] = location_id_bytes_to_compact(sealed_location)

# Populate the sealed status of an atomical
def populate_container_dmint_status(self, atomical):
def populate_container_dmint_status(self, atomical, height: int):
if not atomical.get('$container'):
return
status = self.make_container_dmint_status_by_atomical_id_at_height(atomical['atomical_id'], self.height)
status = self.make_container_dmint_status_by_atomical_id_at_height(atomical['atomical_id'], height)
if not status:
return
atomical['$container_dmint_status'] = status
Expand Down Expand Up @@ -2396,9 +2396,9 @@ def build_atomical_id_to_candidate_map(self, raw_candidate_entries):
return atomical_id_to_candidates_map

# Populate the requested full realm name to provide context for a subrealm request
def populate_request_full_realm_name(self, atomical, pid, request_subrealm):
def populate_request_full_realm_name(self, atomical, pid, request_subrealm, height: Optional[int] = None):
# Resolve the parent realm to get the parent realm path and construct the full_realm_name
parent_realm = self.get_base_mint_info_by_atomical_id(pid)
parent_realm = self.get_base_mint_info_by_atomical_id(pid, height)
if not parent_realm:
atomical_id = atomical['mint_info']['id']
raise IndexError(f'populate_request_full_realm_name: parent realm not found atomical_id={atomical_id}, parent_realm={parent_realm}')
Expand Down Expand Up @@ -2466,11 +2466,10 @@ def build_applicable_rule_map_dmitem(self, all_entries, arg_pid, arg_request_dmi
return applicable_rule_map

# Populate the specific name or request type for containers, tickers, and realms (sub-realms excluded)
def populate_name_subtype_specific_fields(self, atomical, type_str, get_effective_name_func):
def populate_name_subtype_specific_fields(self, atomical, type_str, get_effective_name_func, height: int):
request_name = atomical['mint_info'].get('$request_' + type_str)
if not request_name:
return None, None
height = self.height
status, candidate_id, raw_candidate_entries = get_effective_name_func(request_name, height)
atomical['$' + type_str + '_candidates'] = format_name_type_candidates_to_rpc(raw_candidate_entries, self.build_atomical_id_to_candidate_map(raw_candidate_entries))
atomical['$request_' + type_str + '_status'] = get_name_request_candidate_status(atomical, status, candidate_id, type_str)
Expand All @@ -2479,18 +2478,17 @@ def populate_name_subtype_specific_fields(self, atomical, type_str, get_effectiv
return request_name, status == 'verified' and atomical['atomical_id'] == candidate_id

# Populate the specific subrealm request type information
def populate_subrealm_subtype_specific_fields(self, atomical):
def populate_subrealm_subtype_specific_fields(self, atomical, height: int):
# Check if the effective subrealm is for the current atomical and also resolve its parent.
request_subrealm = atomical['mint_info'].get('$request_subrealm')
if not request_subrealm:
return None, None
pid_compact = atomical['mint_info']['$parent_realm']
pid = compact_to_location_id_bytes(pid_compact)
height = self.height
status, candidate_id, raw_candidate_entries = self.get_effective_subrealm(pid, request_subrealm, height)
atomical['subtype'] = 'request_subrealm' # Will change to 'subrealm' if it is found to be valid
# Populate the requested full realm name
self.populate_request_full_realm_name(atomical, pid, request_subrealm)
self.populate_request_full_realm_name(atomical, pid, request_subrealm, height)
# Build the applicable rule set mapping of atomical_id to the rule that will need to be matched and paid.
# We use this information to display to each candidate what rule would apply to their mint
# and how much to pay and by which block height they must submit their payment
Expand All @@ -2515,7 +2513,7 @@ def populate_subrealm_subtype_specific_fields(self, atomical):
atomical['$request_subrealm'] = atomical['mint_info'].get('$request_subrealm')
atomical['$parent_realm'] = pid_compact
# Resolve the parent realm to get the parent realm path and construct the `full_realm_name`.
parent_realm = self.get_base_mint_info_by_atomical_id(pid)
parent_realm = self.get_base_mint_info_by_atomical_id(pid, height)
if not parent_realm:
atomical_id = atomical['mint_info']['id']
raise IndexError(
Expand All @@ -2538,14 +2536,13 @@ def populate_subrealm_subtype_specific_fields(self, atomical):
return request_subrealm, False

# Populate the specific dmitem request type information
def populate_dmitem_subtype_specific_fields(self, atomical):
def populate_dmitem_subtype_specific_fields(self, atomical, height: int):
# Check if the effective dmitem is for the current atomical and also resolve its parent.
request_dmitem = atomical['mint_info'].get('$request_dmitem')
if not request_dmitem:
return None, None
pid_compact = atomical['mint_info']['$parent_container']
pid = compact_to_location_id_bytes(pid_compact)
height = self.height
status, candidate_id, raw_candidate_entries = self.get_effective_dmitem(pid, request_dmitem, height)
atomical['subtype'] = 'request_dmitem' # Will change to 'dmitem' if it is found to be valid.
# Build the applicable rule set mapping of atomical_id to the rule that will need to be matched and paid.
Expand Down Expand Up @@ -2574,7 +2571,7 @@ def populate_dmitem_subtype_specific_fields(self, atomical):
atomical['$request_dmitem'] = atomical['mint_info'].get('$request_dmitem')
atomical['$parent_container'] = pid_compact
# Resolve the parent to get the parent path and construct the `parent_container_name`.
parent_container = self.get_base_mint_info_by_atomical_id(pid)
parent_container = self.get_base_mint_info_by_atomical_id(pid, height)
if not parent_container:
atomical_id = atomical['mint_info']['id']
raise IndexError(
Expand All @@ -2596,11 +2593,11 @@ def populate_dmitem_subtype_specific_fields(self, atomical):
# 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
def populate_extended_atomical_subtype_info(self, atomical):
def populate_extended_atomical_subtype_info(self, atomical, height: int):
#
# TOP-REALM (TLR) Type Fields
#
the_name_request, is_atomical_name_verified_found = self.populate_name_subtype_specific_fields(atomical, 'realm', self.get_effective_realm)
the_name_request, is_atomical_name_verified_found = self.populate_name_subtype_specific_fields(atomical, 'realm', self.get_effective_realm, height)
if is_atomical_name_verified_found:
atomical['subtype'] = 'realm'
atomical['$realm'] = the_name_request
Expand All @@ -2613,7 +2610,7 @@ def populate_extended_atomical_subtype_info(self, atomical):
#
# CONTAINER Type Fields
#
the_name_request, is_atomical_name_verified_found = self.populate_name_subtype_specific_fields(atomical, 'container', self.get_effective_container)
the_name_request, is_atomical_name_verified_found = self.populate_name_subtype_specific_fields(atomical, 'container', self.get_effective_container, height)
if is_atomical_name_verified_found:
atomical['subtype'] = 'container'
atomical['$container'] = the_name_request
Expand All @@ -2625,7 +2622,7 @@ def populate_extended_atomical_subtype_info(self, atomical):
#
# TICKER NAME FIELDS
#
the_name_request, is_atomical_name_verified_found = self.populate_name_subtype_specific_fields(atomical, 'ticker', self.get_effective_ticker)
the_name_request, is_atomical_name_verified_found = self.populate_name_subtype_specific_fields(atomical, 'ticker', self.get_effective_ticker, height)
if is_atomical_name_verified_found:
atomical['$ticker'] = the_name_request
return atomical
Expand All @@ -2636,12 +2633,13 @@ def populate_extended_atomical_subtype_info(self, atomical):
# SUBREALM type fields
#
# The method populates all the fields and nothing more needs to be done at this level for subrealms
self.populate_subrealm_subtype_specific_fields(atomical)
self.populate_subrealm_subtype_specific_fields(atomical, height)
#
# DMITEM type fields
#
# The method populates all the fields and nothing more needs to be done at this level for dmitems
self.populate_dmitem_subtype_specific_fields(atomical)
self.populate_dmitem_subtype_specific_fields(atomical, height)

return atomical

def is_dft_bitwork_rollover_activated(self, height):
Expand Down Expand Up @@ -3009,18 +3007,18 @@ def advance_txs(
# in one and the same tx as making a payment. It's not advisable to do so, but it's a valid possibility

# Check if there were any payments for subrealms in tx
payment_tx_hash = self.create_or_delete_subname_payment_output_if_valid(tx_hash, tx, tx_num, height, atomicals_operations_found_at_inputs, atomicals_spent_at_inputs, b'spay', self.subrealmpay_data_cache, self.get_expected_subrealm_payment_info, False)
if payment_tx_hash:
subrealm_payment_tx_hash = self.create_or_delete_subname_payment_output_if_valid(tx_hash, tx, tx_num, height, atomicals_operations_found_at_inputs, atomicals_spent_at_inputs, b'spay', self.subrealmpay_data_cache, self.get_expected_subrealm_payment_info, False)
if subrealm_payment_tx_hash:
self.logger.info(f'advance_txs: found valid subrealm payment create_or_delete_subname_payment_output_if_valid {hash_to_hex_str(tx_hash)}')
append_hashX(double_sha256(payment_tx_hash))
append_hashX(double_sha256(subrealm_payment_tx_hash))
self.put_op_data(tx_num, tx_hash, "payment-subrealm")
has_at_least_one_valid_atomicals_operation = True

# Check if there were any payments for dmitems in tx
payment_tx_hash = self.create_or_delete_subname_payment_output_if_valid(tx_hash, tx, tx_num, height, atomicals_operations_found_at_inputs, atomicals_spent_at_inputs, b'dmpay', self.dmpay_data_cache, self.get_expected_dmitem_payment_info, False)
if payment_tx_hash:
dmitem_payment_tx_hash = self.create_or_delete_subname_payment_output_if_valid(tx_hash, tx, tx_num, height, atomicals_operations_found_at_inputs, atomicals_spent_at_inputs, b'dmpay', self.dmpay_data_cache, self.get_expected_dmitem_payment_info, False)
if dmitem_payment_tx_hash:
self.logger.info(f'advance_txs: found valid dmitem payment create_or_delete_subname_payment_output_if_valid {hash_to_hex_str(tx_hash)}')
append_hashX(double_sha256(payment_tx_hash))
append_hashX(double_sha256(dmitem_payment_tx_hash))
self.put_op_data(tx_num, tx_hash, "payment-dmitem")
has_at_least_one_valid_atomicals_operation = True

Expand Down

0 comments on commit 4e530ac

Please sign in to comment.