diff --git a/electrumx/lib/atomicals_blueprint_builder.py b/electrumx/lib/atomicals_blueprint_builder.py index 5b29e12c..e5172826 100644 --- a/electrumx/lib/atomicals_blueprint_builder.py +++ b/electrumx/lib/atomicals_blueprint_builder.py @@ -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]: @@ -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 @@ -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() @@ -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() diff --git a/electrumx/server/block_processor.py b/electrumx/server/block_processor.py index e21447ca..db6f5879 100644 --- a/electrumx/server/block_processor.py +++ b/electrumx/server/block_processor.py @@ -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)}" ) diff --git a/tests/lib/test_atomicals_blueprint_builder.py b/tests/lib/test_atomicals_blueprint_builder.py index 792f63ef..ada4a0a2 100644 --- a/tests/lib/test_atomicals_blueprint_builder.py +++ b/tests/lib/test_atomicals_blueprint_builder.py @@ -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 @@ -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