Skip to content

Commit

Permalink
fix partially colored burn
Browse files Browse the repository at this point in the history
  • Loading branch information
shadowv0vshadow committed Apr 5, 2024
1 parent 76d55ad commit dadbb19
Show file tree
Hide file tree
Showing 5 changed files with 109 additions and 41 deletions.
109 changes: 71 additions & 38 deletions electrumx/lib/atomicals_blueprint_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,6 @@ def build_reverse_output_to_atomical_id_exponent_map(atomical_id_to_output_index

def get_nominal_token_value(value):
return value
# assert(value >= 0)
# assert(exponent >= 0)
# return value / (10**exponent)

def calculate_outputs_to_color_for_ft_atomical_ids(tx, ft_atomicals, sort_by_fifo, is_split_activated) -> FtColoringSummary:
num_fts = len(ft_atomicals.keys())
Expand All @@ -64,15 +61,26 @@ def calculate_outputs_to_color_for_ft_atomical_ids(tx, ft_atomicals, sort_by_fif
for item in atomical_list:
atomical_id = item.atomical_id
# If a target exponent was provided, then use that instead
cleanly_assigned, expected_outputs, remaining_value_from_assign = AtomicalsTransferBlueprintBuilder.assign_expected_outputs_basic(item.total_tokenvalue, tx, next_start_out_idx, False)
if cleanly_assigned and len(expected_outputs) > 0:
next_start_out_idx = expected_outputs[-1] + 1
potential_atomical_ids_to_output_idxs_map[atomical_id] = ExpectedOutputSet(expected_outputs, item.total_tokenvalue)
cleanly_assigned, expected_outputs, remaining_value_from_assign = AtomicalsTransferBlueprintBuilder.assign_expected_outputs_basic(item.total_tokenvalue, tx, next_start_out_idx, is_split_activated)
if not is_split_activated:
if cleanly_assigned and len(expected_outputs) > 0:
next_start_out_idx = expected_outputs[-1] + 1
potential_atomical_ids_to_output_idxs_map[atomical_id] = ExpectedOutputSet(expected_outputs, item.total_tokenvalue)
else:
# Erase the potential for safety
potential_atomical_ids_to_output_idxs_map = {}
non_clean_output_slots = True
break
else:
# Erase the potential for safety
potential_atomical_ids_to_output_idxs_map = {}
non_clean_output_slots = True
break
# no need cleanly_assigned
if len(expected_outputs) > 0:
next_start_out_idx = expected_outputs[-1] + 1
potential_atomical_ids_to_output_idxs_map[atomical_id] = ExpectedOutputSet(expected_outputs, item.total_tokenvalue)
else:
# if no enable uxto
potential_atomical_ids_to_output_idxs_map = {}
non_clean_output_slots = True
break

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

Expand Down Expand Up @@ -307,15 +314,14 @@ def calculate_output_blueprint_fts(cls, tx, ft_atomicals, operations_found_at_in
# Split apart multiple NFT/FT from a UTXO
should_split_ft_atomicals = is_split_operation(operations_found_at_inputs)
if should_split_ft_atomicals:
return AtomicalsTransferBlueprintBuilder.color_ft_atomicals_split(ft_atomicals, operations_found_at_inputs, tx)

return AtomicalsTransferBlueprintBuilder.color_ft_atomicals_split(ft_atomicals, operations_found_at_inputs, tx, is_split_activated)
# Normal assignment in all cases including fall through of failure to provide a target exponent in the above resubstantiation
return AtomicalsTransferBlueprintBuilder.color_ft_atomicals_regular(ft_atomicals, tx, sort_fifo, is_split_activated)

@classmethod
def color_ft_atomicals_split(cls, ft_atomicals, operations_found_at_inputs, tx):
output_colored_map = {}
fts_burned = {}
def color_ft_atomicals_split(cls, ft_atomicals, operations_found_at_inputs, tx, is_split_activated):
output_colored_map = {}
fts_burned = {}
cleanly_assigned = True
for atomical_id, atomical_info in sorted(ft_atomicals.items()):
expected_output_indexes = []
Expand All @@ -331,31 +337,58 @@ def color_ft_atomicals_split(cls, ft_atomicals, operations_found_at_inputs, tx):
if isinstance(total_amount_to_skip_potential, int) and total_amount_to_skip_potential >= 0:
total_amount_to_skip = total_amount_to_skip_potential
total_skipped_so_far = 0
for out_idx, txout in enumerate(tx.outputs):
# If the first output should be skipped and we have not yet done so, then skip/ignore it
if total_amount_to_skip > 0 and total_skipped_so_far < total_amount_to_skip:
total_skipped_so_far += txout.value
continue
# For all remaining outputs attach colors as long as there is adequate remaining_value left to cover the entire output value
if txout.value <= remaining_value:
if is_split_activated:
for out_idx, txout in enumerate(tx.outputs):
# If the first output should be skipped and we have not yet done so, then skip/ignore it
if total_amount_to_skip > 0 and total_skipped_so_far < total_amount_to_skip:
total_skipped_so_far += txout.value
continue
# For all remaining outputs attach colors as long as there is adequate remaining_value left to cover the entire output value
# if txout.value <= remaining_value:
expected_output_indexes.append(out_idx)
if txout.value <= remaining_value:
expected_value = txout.value
else:
expected_value = remaining_value
remaining_value -= txout.value
output_colored_map[out_idx] = output_colored_map.get(out_idx) or {'atomicals': {}}
output_colored_map[out_idx]['atomicals'][atomical_id] = AtomicalColoredOutputFt(txout.value, get_nominal_token_value(txout.value), atomical_info)
output_colored_map[out_idx]['atomicals'][atomical_id] = AtomicalColoredOutputFt(txout.value, expected_value, atomical_info)
# We are done assigning all remaining values
if remaining_value == 0:
break
# Exit case when we have no more remaining_value to assign or the next output is greater than what we have in remaining_value
if txout.value > remaining_value or remaining_value < 0:
cleanly_assigned = False # Used to indicate that all was cleanly assigned
fts_burned[atomical_id] = get_nominal_token_value(remaining_value)
break
# Used to indicate that all was cleanly assigned
if remaining_value != 0:
cleanly_assigned = False
fts_burned[atomical_id] = get_nominal_token_value(remaining_value)

return AtomicalFtOutputBlueprintAssignmentSummary(output_colored_map, fts_burned, cleanly_assigned, None)
# Exit case when we have no more remaining_value to assign or the next output is greater than what we have in remaining_value
if remaining_value < 0:
remaining_value = 0
cleanly_assigned = False # Used to indicate that all was cleanly assigned
break
if remaining_value != 0:
cleanly_assigned = False
fts_burned[atomical_id] = remaining_value
else:
# is_split_activated logic
# use if else keep it simple
for out_idx, txout in enumerate(tx.outputs):
if total_amount_to_skip > 0 and total_skipped_so_far < total_amount_to_skip:
total_skipped_so_far += txout.value
continue
# For all remaining outputs attach colors as long as there is adequate remaining_value left to cover the entire output value
if txout.value <= remaining_value:
expected_output_indexes.append(out_idx)
remaining_value -= txout.value
output_colored_map[out_idx] = output_colored_map.get(out_idx) or {'atomicals': {}}
output_colored_map[out_idx]['atomicals'][atomical_id] = AtomicalColoredOutputFt(txout.value, txout.value, atomical_info)
# We are done assigning all remaining values
if remaining_value == 0:
break
# Exit case when we have no more remaining_value to assign or the next output is greater than what we have in remaining_value
if txout.value > remaining_value or remaining_value < 0:
cleanly_assigned = False # Used to indicate that all was cleanly assigned
fts_burned[atomical_id] = remaining_value
break
if remaining_value != 0:
cleanly_assigned = False
fts_burned[atomical_id] = remaining_value
return AtomicalFtOutputBlueprintAssignmentSummary(output_colored_map, fts_burned, cleanly_assigned, None)

@classmethod
def color_ft_atomicals_regular(cls, ft_atomicals, tx, sort_fifo, is_split_activated):
Expand Down
4 changes: 3 additions & 1 deletion electrumx/server/block_processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -2766,7 +2766,9 @@ def is_density_activated(self, height):

def is_split_activated(self, height):
if height >= self.coin.ATOMICALS_ACTIVATION_SPLIT:
return True
return True
if height <= 0:
return True
return False

# Builds a map of the atomicals spent at a tx
Expand Down
5 changes: 4 additions & 1 deletion electrumx/server/http_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -1988,6 +1988,9 @@ async def get_transaction_detail(self, txid, height=None, tx_num=-1):
return res
if not height:
tx_num, height = self.db.get_tx_num_height_from_tx_hash(tx_hash)
if not tx_num:
height = 0
tx_num = -1

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

if res.get("op"):
if res.get("op") and height > 0:
self.session_mgr._tx_detail_cache[tx_hash] = res

# Recursively encode the result.
Expand Down
3 changes: 3 additions & 0 deletions electrumx/server/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -2803,6 +2803,9 @@ async def get_transaction_detail(self, txid, height=None, tx_num=-1):
return res
if not height:
tx_num, height = self.db.get_tx_num_height_from_tx_hash(tx_hash)
if not tx_num:
height = 0
tx_num = -1

raw_tx = self.db.get_raw_tx_by_tx_hash(tx_hash)
if not raw_tx:
Expand Down
29 changes: 28 additions & 1 deletion tests/lib/test_atomicals_blueprint_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
from electrumx.lib.tx import Tx, TxInput, TxOutput

from electrumx.lib.util_atomicals import (
location_id_bytes_to_compact
location_id_bytes_to_compact,
parse_protocols_operations_from_witness_array
)

coin = Bitcoin
Expand Down Expand Up @@ -449,4 +450,30 @@ def mock_mint_fetcher(self, atomical_id):
assert(len(ft_output_blueprint.outputs) == 2)
assert(ft_output_blueprint.outputs[1]["atomicals"][subject_atomical_id].tokenvalue == 1)
assert(ft_output_blueprint.first_atomical_id == subject_atomical_id)
assert(blueprint_builder.get_are_fts_burned() == False)


def test_spends_ft_y_split_normal():
raw_tx_str = '0100000000010213ac24b68388e0e32f3b19e95764c67d03b151d1f524eb07bc6e4f2790a3b7f00000000000ffffffff2423c79220c41bd904699aada54868e5c5aecb15168971964c6f5950a7b1d6860000000000ffffffff03e80300000000000022512011b6ce99eab0d8873d787e99e68a351358228893cdf1049ac48aae51391598abe80300000000000022512011b6ce99eab0d8873d787e99e68a351358228893cdf1049ac48aae51391598abe80300000000000022512011b6ce99eab0d8873d787e99e68a351358228893cdf1049ac48aae51391598ab03401aaa5ca0d475dcec02867f28f687494a639b3b43aff0a776c68d94f8cd3e987bb08a3463d8ab937f18f5dadfc916337b2df98cdd700b8514c6fdaff7f5ddffc975201764381bc0b54064cc55a0dda055c5e9875e5cdd7a7c1452d9b93d6015546170ac00630461746f6d017948a178423935323765666134333236323633366438663539313766633736336662646430393333336534623338376166643664346564376139303561313237623237623469301903e86821c01764381bc0b54064cc55a0dda055c5e9875e5cdd7a7c1452d9b93d60155461700140101db7c999f69c7f551d6800341a75ae659e8c100d1bb116b0935afc9ac3aec69bb97eed3ea72fa75912401400aa53f85f8a862f0f672620f31c5e704d8b4d5c00000000'
raw_tx = bytes.fromhex(raw_tx_str)
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'
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"
tx, tx_hash = coin.DESERIALIZER(raw_tx, 0).read_tx_and_hash()
operation_found_at_inputs = parse_protocols_operations_from_witness_array(tx, tx_hash, True)
# print(operation_found_at_inputs)
atomicals_spent_at_inputs= {
1: [{'atomical_id': subject_atomical_id1, 'location_id': b'not_used', 'data': b'not_used', 'data_value': {'satvalue': 1000, 'tokenvalue': 1000}},
{'atomical_id': subject_atomical_id, 'location_id': b'not_used', 'data': b'not_used', 'data_value': {'satvalue': 1000, 'tokenvalue': 1000}}]
}
def mock_mint_fetcher(self, atomical_id):
return {
'atomical_id': atomical_id,
'type': 'FT'
}
blueprint_builder = AtomicalsTransferBlueprintBuilder(MockLogger(), atomicals_spent_at_inputs, operation_found_at_inputs, tx_hash, tx, mock_mint_fetcher, True, False)
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) == 2)
assert(ft_output_blueprint.fts_burned == {})
assert(blueprint_builder.get_are_fts_burned() == False)

0 comments on commit dadbb19

Please sign in to comment.