Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix Atomical value recognition in payments (#203) #204

Merged
merged 2 commits into from
Jun 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 7 additions & 27 deletions electrumx/lib/atomicals_blueprint_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,6 @@ def build_reverse_output_to_atomical_id_exponent_map(atomical_id_to_output_index
return reverse_mapped


def get_nominal_atomical_value(value):
return value


def calculate_outputs_to_color_for_ft_atomical_ids(
tx, ft_atomicals, sort_by_fifo, is_custom_coloring_activated
) -> Optional[FtColoringSummary]:
Expand Down Expand Up @@ -789,12 +785,6 @@ def build_atomical_input_summaries(
# For each input atomical spent at the current input...
for atomicals_entry in atomicals_entry_list:
atomical_id = atomicals_entry["atomical_id"]
# value, = unpack_le_uint64(
# atomicals_entry['data'][HASHX_LEN + SCRIPTHASH_LEN : HASHX_LEN + SCRIPTHASH_LEN + 8]
# )
# exponent, = unpack_le_uint16_from(
# atomicals_entry['data'][HASHX_LEN + SCRIPTHASH_LEN + 8: HASHX_LEN + SCRIPTHASH_LEN + 8 + 8]
# )
sat_value = atomicals_entry["data_value"]["sat_value"]
atomical_value = atomicals_entry["data_value"]["atomical_value"]
# Perform a cache lookup for the mint information since we do not want to query multiple times
Expand Down Expand Up @@ -962,19 +952,6 @@ def are_payments_satisfied(self, expected_payment_outputs):
# Map the output script hex only
expected_output_keys_satisfied[output_script_key] = False

# Prepare the mapping of which ARC20 is paid at which output
ft_coloring_summary = calculate_outputs_to_color_for_ft_atomical_ids(
self.tx,
self.ft_atomicals,
self.sort_fifo,
self.is_custom_coloring_activated,
)
output_idx_to_atomical_id_map = {}
if ft_coloring_summary:
output_idx_to_atomical_id_map = build_reverse_output_to_atomical_id_exponent_map(
ft_coloring_summary.atomical_id_to_expected_outs_map
)

# For each of the outputs, assess whether it matches any of the required payment output expectations
for idx, txout in enumerate(self.tx.outputs):
output_script_hex = txout.pk_script.hex()
Expand Down Expand Up @@ -1004,12 +981,15 @@ def are_payments_satisfied(self, expected_payment_outputs):
expected_output_payment_id_type
)
# Check in the reverse map if the current output idx is colored with the expected color
output_summary = output_idx_to_atomical_id_map.get(idx)
if output_summary and output_summary.get(expected_output_payment_id_type_long_form, None) is not None:
output_summary = (
self.ft_output_blueprint.outputs.get(idx, {})
.get("atomicals", {})
.get(expected_output_payment_id_type_long_form, None)
)
if output_summary:
# Ensure the normalized atomical_value is greater than
# or equal to the expected payment amount in that token type.
# exponent_for_for_atomical_id = output_summary.get(expected_output_payment_id_type_long_form)
atomical_value = get_nominal_atomical_value(txout.value)
atomical_value = output_summary.atomical_value
if atomical_value >= expected_output_payment_value:
# Mark that the output was matched at least once
key = output_script_hex + expected_output_payment_id_type_long_form.hex()
Expand Down
5 changes: 4 additions & 1 deletion electrumx/server/block_processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -3897,7 +3897,10 @@ def create_or_delete_subname_payment_output_if_valid(
f"create_or_delete_subname_payment_output_if_valid: valid pattern failed. DeveloperError request_subname={request_subname}, regex={regex}"
)

if not blueprint_builder.are_payments_satisfied(matched_price_point["matched_rule"].get("o")):
if not blueprint_builder.are_payments_satisfied(
matched_price_point["matched_rule"].get("o"),
atomicals_spent_at_inputs,
):
self.logger.warning(
f"create_or_delete_subname_payment_output_if_valid: payments not satisfied. request_subname={request_subname}, regex={regex} atomical_id_for_payment={location_id_bytes_to_compact(atomical_id_for_payment)}"
)
Expand Down
86 changes: 83 additions & 3 deletions tests/lib/test_atomicals_blueprint_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -599,9 +599,6 @@ def mock_mint_fetcher(self, atomical_id):
nft_output_blueprint = blueprint_builder.get_nft_output_blueprint()
assert len(nft_output_blueprint.outputs) == 0
ft_output_blueprint = blueprint_builder.get_ft_output_blueprint()
for k, v in ft_output_blueprint.outputs.items():
for i, ii in v["atomicals"].items():
print(ii.__dict__)
assert ft_output_blueprint.cleanly_assigned == True
assert blueprint_builder.get_are_fts_burned() == False

Expand Down Expand Up @@ -1693,3 +1690,86 @@ def mock_mint_fetcher(self, atomical_id):
assert len(ft_output_blueprint.outputs) == 0
assert ft_output_blueprint.fts_burned == {}
assert blueprint_builder.get_are_fts_burned() == False


def test_partially_colored_spends_are_payments_satisfied_checks():
raw_tx_str = "02000000000101647760b13086a2f2e77395e474305237afa65ec638dda01132c8c48c8b891fd00000000000ffffffff03a8610000000000002251208a586070907d75b89f1b7bcbe8dd5c623e0143e9b62d5d6759da06a59b749679a861000000000000225120ed2ec645d1749c9b2dba88b1346899c60c82f7a57e6359964393a2bba31450f200000000000000002d6a0461746f6d017024921bd27146f57d42565b373214ae7f6d05fa85c3f73eeb5dd876c4c81be58888000000000140d94db131ec889cb33fc258bc3bb5ace3656597cde88cf51494ae864f171915d262a50af24e3699560116450c4244a99b7d84602b8be1fe4c640250d2202330c800000000"
raw_tx = bytes.fromhex(raw_tx_str)
subject_atomical_id = (
b"A\x03\x8f'\xe7\x85`l\xa0\xcc\x1e\xfd\x8e:\xa9\x12\xa1\\r\xd0o5\x9a\xeb\x05$=\xab+p\xa8V\x01\x00\x00\x00"
)

tx, tx_hash = coin.DESERIALIZER(raw_tx, 0).read_tx_and_hash()
atomicals_spent_at_inputs = {
1: [
{
"atomical_id": subject_atomical_id,
"location_id": b"not_used",
"data": b"not_used",
"data_value": {"sat_value": 50000, "atomical_value": 2},
},
],
}

def mock_mint_fetcher(self, atomical_id):
return {"atomical_id": atomical_id, "type": "FT"}

operations_at_inputs = {}
blueprint_builder = AtomicalsTransferBlueprintBuilder(
MockLogger(),
atomicals_spent_at_inputs,
operations_at_inputs,
tx_hash,
tx,
mock_mint_fetcher,
True,
True,
)
nft_output_blueprint = blueprint_builder.get_nft_output_blueprint()
assert len(nft_output_blueprint.outputs) == 0
ft_output_blueprint = blueprint_builder.get_ft_output_blueprint()
assert len(ft_output_blueprint.outputs) == 1
assert ft_output_blueprint.cleanly_assigned == False
assert blueprint_builder.get_are_fts_burned() == False

subject_atomical_id_compact = location_id_bytes_to_compact(subject_atomical_id)
# Empty rules
rules = {}
payment_valid = blueprint_builder.are_payments_satisfied(rules)
assert not payment_valid
# Valid payment to one output
rules = {
"51208a586070907d75b89f1b7bcbe8dd5c623e0143e9b62d5d6759da06a59b749679": {
"id": subject_atomical_id_compact,
"v": 2,
}
}
payment_valid = blueprint_builder.are_payments_satisfied(rules)
assert payment_valid
# Invalid payment insufficient amount
rules = {
"51208a586070907d75b89f1b7bcbe8dd5c623e0143e9b62d5d6759da06a59b749679": {
"id": subject_atomical_id_compact,
"v": 3,
}
}
payment_valid = blueprint_builder.are_payments_satisfied(rules)
assert not payment_valid
# Valid payment higher amount
rules = {
"51208a586070907d75b89f1b7bcbe8dd5c623e0143e9b62d5d6759da06a59b749679": {
"id": subject_atomical_id_compact,
"v": 1,
}
}
payment_valid = blueprint_builder.are_payments_satisfied(rules)
assert payment_valid
# Invalid payment to wrong address
rules = {
"51208a586070907d75b89f1b7bcbe8dd5c623e0143e9b62d5d6759da06a59b749678": {
"id": subject_atomical_id_compact,
"v": 2,
}
}
payment_valid = blueprint_builder.are_payments_satisfied(rules)
assert not payment_valid
Loading