Skip to content

Commit dadbb19

Browse files
fix partially colored burn
1 parent 76d55ad commit dadbb19

File tree

5 files changed

+109
-41
lines changed

5 files changed

+109
-41
lines changed

electrumx/lib/atomicals_blueprint_builder.py

Lines changed: 71 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,6 @@ def build_reverse_output_to_atomical_id_exponent_map(atomical_id_to_output_index
4747

4848
def get_nominal_token_value(value):
4949
return value
50-
# assert(value >= 0)
51-
# assert(exponent >= 0)
52-
# return value / (10**exponent)
5350

5451
def calculate_outputs_to_color_for_ft_atomical_ids(tx, ft_atomicals, sort_by_fifo, is_split_activated) -> FtColoringSummary:
5552
num_fts = len(ft_atomicals.keys())
@@ -64,15 +61,26 @@ def calculate_outputs_to_color_for_ft_atomical_ids(tx, ft_atomicals, sort_by_fif
6461
for item in atomical_list:
6562
atomical_id = item.atomical_id
6663
# If a target exponent was provided, then use that instead
67-
cleanly_assigned, expected_outputs, remaining_value_from_assign = AtomicalsTransferBlueprintBuilder.assign_expected_outputs_basic(item.total_tokenvalue, tx, next_start_out_idx, False)
68-
if cleanly_assigned and len(expected_outputs) > 0:
69-
next_start_out_idx = expected_outputs[-1] + 1
70-
potential_atomical_ids_to_output_idxs_map[atomical_id] = ExpectedOutputSet(expected_outputs, item.total_tokenvalue)
64+
cleanly_assigned, expected_outputs, remaining_value_from_assign = AtomicalsTransferBlueprintBuilder.assign_expected_outputs_basic(item.total_tokenvalue, tx, next_start_out_idx, is_split_activated)
65+
if not is_split_activated:
66+
if cleanly_assigned and len(expected_outputs) > 0:
67+
next_start_out_idx = expected_outputs[-1] + 1
68+
potential_atomical_ids_to_output_idxs_map[atomical_id] = ExpectedOutputSet(expected_outputs, item.total_tokenvalue)
69+
else:
70+
# Erase the potential for safety
71+
potential_atomical_ids_to_output_idxs_map = {}
72+
non_clean_output_slots = True
73+
break
7174
else:
72-
# Erase the potential for safety
73-
potential_atomical_ids_to_output_idxs_map = {}
74-
non_clean_output_slots = True
75-
break
75+
# no need cleanly_assigned
76+
if len(expected_outputs) > 0:
77+
next_start_out_idx = expected_outputs[-1] + 1
78+
potential_atomical_ids_to_output_idxs_map[atomical_id] = ExpectedOutputSet(expected_outputs, item.total_tokenvalue)
79+
else:
80+
# if no enable uxto
81+
potential_atomical_ids_to_output_idxs_map = {}
82+
non_clean_output_slots = True
83+
break
7684

7785
# If the output slots did not fit cleanly, then default to just assigning everything from the 0'th output index
7886
if non_clean_output_slots:
@@ -81,9 +89,8 @@ def calculate_outputs_to_color_for_ft_atomical_ids(tx, ft_atomicals, sort_by_fif
8189
atomical_id = item.atomical_id
8290
cleanly_assigned, expected_outputs, remaining_value_from_assign = AtomicalsTransferBlueprintBuilder.assign_expected_outputs_basic(item.total_tokenvalue, tx, 0, is_split_activated)
8391
potential_atomical_ids_to_output_idxs_map[atomical_id] = ExpectedOutputSet(expected_outputs, item.total_tokenvalue)
84-
if not is_split_activated:
85-
if not cleanly_assigned and remaining_value_from_assign > 0:
86-
fts_burned[atomical_id] = get_nominal_token_value(remaining_value_from_assign)
92+
if remaining_value_from_assign > 0:
93+
fts_burned[atomical_id] = remaining_value_from_assign
8794
return FtColoringSummary(potential_atomical_ids_to_output_idxs_map, fts_burned, not non_clean_output_slots, atomical_list)
8895
return FtColoringSummary(potential_atomical_ids_to_output_idxs_map, fts_burned, not non_clean_output_slots, atomical_list)
8996

@@ -307,15 +314,14 @@ def calculate_output_blueprint_fts(cls, tx, ft_atomicals, operations_found_at_in
307314
# Split apart multiple NFT/FT from a UTXO
308315
should_split_ft_atomicals = is_split_operation(operations_found_at_inputs)
309316
if should_split_ft_atomicals:
310-
return AtomicalsTransferBlueprintBuilder.color_ft_atomicals_split(ft_atomicals, operations_found_at_inputs, tx)
311-
317+
return AtomicalsTransferBlueprintBuilder.color_ft_atomicals_split(ft_atomicals, operations_found_at_inputs, tx, is_split_activated)
312318
# Normal assignment in all cases including fall through of failure to provide a target exponent in the above resubstantiation
313319
return AtomicalsTransferBlueprintBuilder.color_ft_atomicals_regular(ft_atomicals, tx, sort_fifo, is_split_activated)
314320

315321
@classmethod
316-
def color_ft_atomicals_split(cls, ft_atomicals, operations_found_at_inputs, tx):
317-
output_colored_map = {}
318-
fts_burned = {}
322+
def color_ft_atomicals_split(cls, ft_atomicals, operations_found_at_inputs, tx, is_split_activated):
323+
output_colored_map = {}
324+
fts_burned = {}
319325
cleanly_assigned = True
320326
for atomical_id, atomical_info in sorted(ft_atomicals.items()):
321327
expected_output_indexes = []
@@ -331,31 +337,58 @@ def color_ft_atomicals_split(cls, ft_atomicals, operations_found_at_inputs, tx):
331337
if isinstance(total_amount_to_skip_potential, int) and total_amount_to_skip_potential >= 0:
332338
total_amount_to_skip = total_amount_to_skip_potential
333339
total_skipped_so_far = 0
334-
for out_idx, txout in enumerate(tx.outputs):
335-
# If the first output should be skipped and we have not yet done so, then skip/ignore it
336-
if total_amount_to_skip > 0 and total_skipped_so_far < total_amount_to_skip:
337-
total_skipped_so_far += txout.value
338-
continue
339-
# For all remaining outputs attach colors as long as there is adequate remaining_value left to cover the entire output value
340-
if txout.value <= remaining_value:
340+
if is_split_activated:
341+
for out_idx, txout in enumerate(tx.outputs):
342+
# If the first output should be skipped and we have not yet done so, then skip/ignore it
343+
if total_amount_to_skip > 0 and total_skipped_so_far < total_amount_to_skip:
344+
total_skipped_so_far += txout.value
345+
continue
346+
# For all remaining outputs attach colors as long as there is adequate remaining_value left to cover the entire output value
347+
# if txout.value <= remaining_value:
341348
expected_output_indexes.append(out_idx)
349+
if txout.value <= remaining_value:
350+
expected_value = txout.value
351+
else:
352+
expected_value = remaining_value
342353
remaining_value -= txout.value
343354
output_colored_map[out_idx] = output_colored_map.get(out_idx) or {'atomicals': {}}
344-
output_colored_map[out_idx]['atomicals'][atomical_id] = AtomicalColoredOutputFt(txout.value, get_nominal_token_value(txout.value), atomical_info)
355+
output_colored_map[out_idx]['atomicals'][atomical_id] = AtomicalColoredOutputFt(txout.value, expected_value, atomical_info)
345356
# We are done assigning all remaining values
346357
if remaining_value == 0:
347358
break
348-
# Exit case when we have no more remaining_value to assign or the next output is greater than what we have in remaining_value
349-
if txout.value > remaining_value or remaining_value < 0:
350-
cleanly_assigned = False # Used to indicate that all was cleanly assigned
351-
fts_burned[atomical_id] = get_nominal_token_value(remaining_value)
352-
break
353-
# Used to indicate that all was cleanly assigned
354-
if remaining_value != 0:
355-
cleanly_assigned = False
356-
fts_burned[atomical_id] = get_nominal_token_value(remaining_value)
357-
358-
return AtomicalFtOutputBlueprintAssignmentSummary(output_colored_map, fts_burned, cleanly_assigned, None)
359+
# Exit case when we have no more remaining_value to assign or the next output is greater than what we have in remaining_value
360+
if remaining_value < 0:
361+
remaining_value = 0
362+
cleanly_assigned = False # Used to indicate that all was cleanly assigned
363+
break
364+
if remaining_value != 0:
365+
cleanly_assigned = False
366+
fts_burned[atomical_id] = remaining_value
367+
else:
368+
# is_split_activated logic
369+
# use if else keep it simple
370+
for out_idx, txout in enumerate(tx.outputs):
371+
if total_amount_to_skip > 0 and total_skipped_so_far < total_amount_to_skip:
372+
total_skipped_so_far += txout.value
373+
continue
374+
# For all remaining outputs attach colors as long as there is adequate remaining_value left to cover the entire output value
375+
if txout.value <= remaining_value:
376+
expected_output_indexes.append(out_idx)
377+
remaining_value -= txout.value
378+
output_colored_map[out_idx] = output_colored_map.get(out_idx) or {'atomicals': {}}
379+
output_colored_map[out_idx]['atomicals'][atomical_id] = AtomicalColoredOutputFt(txout.value, txout.value, atomical_info)
380+
# We are done assigning all remaining values
381+
if remaining_value == 0:
382+
break
383+
# Exit case when we have no more remaining_value to assign or the next output is greater than what we have in remaining_value
384+
if txout.value > remaining_value or remaining_value < 0:
385+
cleanly_assigned = False # Used to indicate that all was cleanly assigned
386+
fts_burned[atomical_id] = remaining_value
387+
break
388+
if remaining_value != 0:
389+
cleanly_assigned = False
390+
fts_burned[atomical_id] = remaining_value
391+
return AtomicalFtOutputBlueprintAssignmentSummary(output_colored_map, fts_burned, cleanly_assigned, None)
359392

360393
@classmethod
361394
def color_ft_atomicals_regular(cls, ft_atomicals, tx, sort_fifo, is_split_activated):

electrumx/server/block_processor.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2766,7 +2766,9 @@ def is_density_activated(self, height):
27662766

27672767
def is_split_activated(self, height):
27682768
if height >= self.coin.ATOMICALS_ACTIVATION_SPLIT:
2769-
return True
2769+
return True
2770+
if height <= 0:
2771+
return True
27702772
return False
27712773

27722774
# Builds a map of the atomicals spent at a tx

electrumx/server/http_session.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1988,6 +1988,9 @@ async def get_transaction_detail(self, txid, height=None, tx_num=-1):
19881988
return res
19891989
if not height:
19901990
tx_num, height = self.db.get_tx_num_height_from_tx_hash(tx_hash)
1991+
if not tx_num:
1992+
height = 0
1993+
tx_num = -1
19911994

19921995
res = {}
19931996
raw_tx = self.db.get_raw_tx_by_tx_hash(tx_hash)
@@ -2210,7 +2213,7 @@ async def get_transaction_detail(self, txid, height=None, tx_num=-1):
22102213
if entity_type == 'dmitem':
22112214
res["op"] = "payment-dmitem"
22122215

2213-
if res.get("op"):
2216+
if res.get("op") and height > 0:
22142217
self.session_mgr._tx_detail_cache[tx_hash] = res
22152218

22162219
# Recursively encode the result.

electrumx/server/session.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2803,6 +2803,9 @@ async def get_transaction_detail(self, txid, height=None, tx_num=-1):
28032803
return res
28042804
if not height:
28052805
tx_num, height = self.db.get_tx_num_height_from_tx_hash(tx_hash)
2806+
if not tx_num:
2807+
height = 0
2808+
tx_num = -1
28062809

28072810
raw_tx = self.db.get_raw_tx_by_tx_hash(tx_hash)
28082811
if not raw_tx:

tests/lib/test_atomicals_blueprint_builder.py

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
from electrumx.lib.tx import Tx, TxInput, TxOutput
77

88
from electrumx.lib.util_atomicals import (
9-
location_id_bytes_to_compact
9+
location_id_bytes_to_compact,
10+
parse_protocols_operations_from_witness_array
1011
)
1112

1213
coin = Bitcoin
@@ -449,4 +450,30 @@ def mock_mint_fetcher(self, atomical_id):
449450
assert(len(ft_output_blueprint.outputs) == 2)
450451
assert(ft_output_blueprint.outputs[1]["atomicals"][subject_atomical_id].tokenvalue == 1)
451452
assert(ft_output_blueprint.first_atomical_id == subject_atomical_id)
453+
assert(blueprint_builder.get_are_fts_burned() == False)
454+
455+
456+
def test_spends_ft_y_split_normal():
457+
raw_tx_str = '0100000000010213ac24b68388e0e32f3b19e95764c67d03b151d1f524eb07bc6e4f2790a3b7f00000000000ffffffff2423c79220c41bd904699aada54868e5c5aecb15168971964c6f5950a7b1d6860000000000ffffffff03e80300000000000022512011b6ce99eab0d8873d787e99e68a351358228893cdf1049ac48aae51391598abe80300000000000022512011b6ce99eab0d8873d787e99e68a351358228893cdf1049ac48aae51391598abe80300000000000022512011b6ce99eab0d8873d787e99e68a351358228893cdf1049ac48aae51391598ab03401aaa5ca0d475dcec02867f28f687494a639b3b43aff0a776c68d94f8cd3e987bb08a3463d8ab937f18f5dadfc916337b2df98cdd700b8514c6fdaff7f5ddffc975201764381bc0b54064cc55a0dda055c5e9875e5cdd7a7c1452d9b93d6015546170ac00630461746f6d017948a178423935323765666134333236323633366438663539313766633736336662646430393333336534623338376166643664346564376139303561313237623237623469301903e86821c01764381bc0b54064cc55a0dda055c5e9875e5cdd7a7c1452d9b93d60155461700140101db7c999f69c7f551d6800341a75ae659e8c100d1bb116b0935afc9ac3aec69bb97eed3ea72fa75912401400aa53f85f8a862f0f672620f31c5e704d8b4d5c00000000'
458+
raw_tx = bytes.fromhex(raw_tx_str)
459+
subject_atomical_id = b'\x13Jv:\xb1\xad\x9a\xaf\x8a#[7\xa9s\xc0\xcc\xb2\xca\xe1"\x05Y\xc8s\x87\x11\xcc\x90W\xe2\x88\x88\x00\x00\x00\x00'
460+
subject_atomical_id1 = b"\xb4'{\x12Z\x90z\xed\xd4\xd6\xaf\x87\xb3\xe43\x93\xd0\xbd?v\xfc\x17Y\x8fmcb2\xa4\xef'\x95\x00\x00\x00\x00"
461+
tx, tx_hash = coin.DESERIALIZER(raw_tx, 0).read_tx_and_hash()
462+
operation_found_at_inputs = parse_protocols_operations_from_witness_array(tx, tx_hash, True)
463+
# print(operation_found_at_inputs)
464+
atomicals_spent_at_inputs= {
465+
1: [{'atomical_id': subject_atomical_id1, 'location_id': b'not_used', 'data': b'not_used', 'data_value': {'satvalue': 1000, 'tokenvalue': 1000}},
466+
{'atomical_id': subject_atomical_id, 'location_id': b'not_used', 'data': b'not_used', 'data_value': {'satvalue': 1000, 'tokenvalue': 1000}}]
467+
}
468+
def mock_mint_fetcher(self, atomical_id):
469+
return {
470+
'atomical_id': atomical_id,
471+
'type': 'FT'
472+
}
473+
blueprint_builder = AtomicalsTransferBlueprintBuilder(MockLogger(), atomicals_spent_at_inputs, operation_found_at_inputs, tx_hash, tx, mock_mint_fetcher, True, False)
474+
nft_output_blueprint = blueprint_builder.get_nft_output_blueprint()
475+
assert(len(nft_output_blueprint.outputs) == 0)
476+
ft_output_blueprint = blueprint_builder.get_ft_output_blueprint()
477+
assert(len(ft_output_blueprint.outputs) == 2)
478+
assert(ft_output_blueprint.fts_burned == {})
452479
assert(blueprint_builder.get_are_fts_burned() == False)

0 commit comments

Comments
 (0)