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

[Split] partially colored #161

Merged
merged 28 commits into from
May 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
031cf8f
split
shadowv0vshadow Apr 1, 2024
54d570d
add is_split_activated height
shadowv0vshadow Apr 1, 2024
75cb91d
update code
shadowv0vshadow Apr 1, 2024
0111ecf
set tokenvalue
shadowv0vshadow Apr 1, 2024
449cd50
add multiple expected_outputs case
shadowv0vshadow Apr 1, 2024
9ed3de0
add some comment
shadowv0vshadow Apr 1, 2024
c12d36f
fix payment error
shadowv0vshadow Apr 1, 2024
0dbb2c3
update set value to satvalue and tokenvalue
shadowv0vshadow Apr 1, 2024
f0308cf
fix atomicals_listscripthash relevant methods and add tokenvalue in r…
shadowv0vshadow Apr 1, 2024
aa7fad8
fix transaction_detail add tokenvalue
shadowv0vshadow Apr 1, 2024
558bd2d
add split one token testcase
shadowv0vshadow Apr 1, 2024
76d55ad
fix uxto not found bug
shadowv0vshadow Apr 2, 2024
dadbb19
fix partially colored burn
shadowv0vshadow Apr 5, 2024
2326386
fix y split partially colored
shadowv0vshadow Apr 5, 2024
d7a9373
fix utxo cleanly assigned and update testcase
shadowv0vshadow Apr 5, 2024
8bed934
update balance and listunspent return, display utxo's tokenvalue
shadowv0vshadow Apr 6, 2024
27409ac
remove validate_ft_rules_raw_tx cleanly_assigned and fix cleanly_assi…
shadowv0vshadow Apr 7, 2024
e618386
fix testcase
shadowv0vshadow Apr 7, 2024
83dead9
💬 Use `sat_value` and `atomical_value` for solid readability
AlexV525 Apr 8, 2024
b5501e2
🎨 Format test
AlexV525 Apr 8, 2024
03b921c
⚡️ Improve `listscripthash`
AlexV525 Apr 8, 2024
da5cc34
⚡️ Improve `listunspent`
AlexV525 Apr 8, 2024
60f94c3
🔥 Remove `sat_value` from `listscripthash`
AlexV525 Apr 8, 2024
3356374
🔥 Remove more `sat_value`
AlexV525 Apr 9, 2024
e5ffcb1
Merge branch 'develop' into split-utxo
shadowv0vshadow Apr 24, 2024
59cfb44
add location atomical value
shadowv0vshadow Apr 24, 2024
bfebebf
Merge branch 'develop' into split-utxo
shadowv0vshadow May 15, 2024
1ccd62d
update ATOMICALS_ACTIVATION_SPLIT
shadowv0vshadow May 15, 2024
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
343 changes: 197 additions & 146 deletions electrumx/lib/atomicals_blueprint_builder.py

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions electrumx/lib/coins.py
Original file line number Diff line number Diff line change
Expand Up @@ -675,6 +675,7 @@ class Bitcoin(BitcoinMixin, AtomicalsCoinMixin, Coin):
ATOMICALS_ACTIVATION_HEIGHT_COMMITZ = 822800
ATOMICALS_ACTIVATION_HEIGHT_DENSITY = 828128
ATOMICALS_ACTIVATION_HEIGHT_DFT_BITWORK_ROLLOVER = 828628
ATOMICALS_ACTIVATION_SPLIT = 845000

@classmethod
def warn_old_client_on_tx_broadcast(cls, client_ver):
Expand Down Expand Up @@ -943,6 +944,7 @@ class BitcoinTestnet(BitcoinTestnetMixin, AtomicalsCoinMixin, Coin):
ATOMICALS_ACTIVATION_HEIGHT_COMMITZ = 2543936
ATOMICALS_ACTIVATION_HEIGHT_DENSITY = 2572729
ATOMICALS_ACTIVATION_HEIGHT_DFT_BITWORK_ROLLOVER = 2576412
ATOMICALS_ACTIVATION_SPLIT = 2584936

@classmethod
def warn_old_client_on_tx_broadcast(cls, client_ver):
Expand Down
12 changes: 6 additions & 6 deletions electrumx/lib/util_atomicals.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@

from array import array
from electrumx.lib.script import OpCodes, ScriptError, Script, is_unspendable_legacy, is_unspendable_genesis, SCRIPTHASH_LEN
from electrumx.lib.util import pack_le_uint64, unpack_le_uint16_from, unpack_le_uint64, unpack_le_uint32, unpack_le_uint32_from, pack_le_uint16, pack_le_uint32
from electrumx.lib.util import pack_le_uint64, unpack_le_uint16_from, unpack_le_uint64, unpack_le_uint32, unpack_le_uint32_from, pack_le_uint16, pack_le_uint32, unpack_le_uint64_from
from electrumx.lib.hash import hash_to_hex_str, hex_str_to_hash, double_sha256, HASHX_LEN
import re
import os
Expand Down Expand Up @@ -436,7 +436,7 @@ def build_base_mint_info(commit_txid, commit_index, reveal_location_txid, reveal
output_idx_le = pack_le_uint32(expected_output_index)
atomical_id = commit_txid + pack_le_uint32(commit_index)
location = reveal_location_txid + pack_le_uint32(reveal_location_index)
value_sats = pack_le_uint64(txout.value)
# sat_value = pack_le_uint64(txout.value)
# Create the general mint information
encoder = krock32.Encoder(checksum=False)
commit_txid_reversed = bytearray(commit_txid)
Expand Down Expand Up @@ -1805,11 +1805,11 @@ def is_mint_pow_valid(txid, mint_pow_commit):
return False

def expand_spend_utxo_data(data):
value, = unpack_le_uint64(data[HASHX_LEN + SCRIPTHASH_LEN : HASHX_LEN + SCRIPTHASH_LEN + 8])
exponent, = unpack_le_uint16_from(data[HASHX_LEN + SCRIPTHASH_LEN + 8: HASHX_LEN + SCRIPTHASH_LEN + 8 + 2])
sat_value, = unpack_le_uint64(data[HASHX_LEN + SCRIPTHASH_LEN: HASHX_LEN + SCRIPTHASH_LEN + 8])
atomical_value, = unpack_le_uint64_from(data[HASHX_LEN + SCRIPTHASH_LEN + 8: HASHX_LEN + SCRIPTHASH_LEN + 8 + 8])
return {
'value': value,
'exponent': exponent
'sat_value': sat_value,
'atomical_value': atomical_value
}

def validate_dmitem_mint_args_with_container_dmint(mint_args, mint_data_payload, dmint):
Expand Down
80 changes: 50 additions & 30 deletions electrumx/server/block_processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -548,10 +548,10 @@ def validate_ft_rules_raw_tx(self, raw_tx):
# Build a structure of organizing into NFT and FTs
# Note: We do not validate anything with NFTs, just FTs
# Build the "blueprint" for how to assign all atomicals
blueprint_builder = AtomicalsTransferBlueprintBuilder(self.logger, atomicals_spent_at_inputs, operations_found_at_inputs, tx_hash, tx, self.get_atomicals_id_mint_info, True)
blueprint_builder = AtomicalsTransferBlueprintBuilder(self.logger, atomicals_spent_at_inputs, operations_found_at_inputs, tx_hash, tx, self.get_atomicals_id_mint_info, True, self.is_split_activated(self.height))
ft_output_blueprint = blueprint_builder.get_ft_output_blueprint()
# Log that there were tokens burned due to not being cleanly assigned
if blueprint_builder.get_are_fts_burned() or not blueprint_builder.cleanly_assigned:
if blueprint_builder.get_are_fts_burned():
encoded_atomicals_spent_at_inputs = encode_atomical_ids_hex(atomicals_spent_at_inputs)
encoded_ft_output_blueprint = auto_encode_bytes_items(encode_atomical_ids_hex(ft_output_blueprint))
outputs = encoded_ft_output_blueprint['outputs']
Expand Down Expand Up @@ -913,7 +913,7 @@ def spend_atomicals_utxo(self, tx_hash: bytes, tx_idx: int, live_run) -> bytes:
'atomical_id': key,
'location_id': location_id,
'data': value,
'data_ex': expand_spend_utxo_data(value)
'data_value': expand_spend_utxo_data(value)
})
if live_run:
value_with_tombstone['found_in_cache'] = True
Expand Down Expand Up @@ -946,7 +946,7 @@ def spend_atomicals_utxo(self, tx_hash: bytes, tx_idx: int, live_run) -> bytes:
'atomical_id': atomical_id,
'location_id': location_id,
'data': atomical_i_db_value,
'data_ex': expand_spend_utxo_data(atomical_i_db_value)
'data_value': expand_spend_utxo_data(atomical_i_db_value) # expand spend uxto data for token value
})

# Return all of the atomicals spent at the address
Expand Down Expand Up @@ -1077,22 +1077,28 @@ def log_can_be_created(self, method, msg, subject, validity, val):
def validate_and_create_nft_mint_utxo(self, mint_info, txout, height, tx_hash):
if not mint_info or not isinstance(mint_info, dict):
return False
value_sats = pack_le_uint64(mint_info['reveal_location_value'])
sat_value = pack_le_uint64(mint_info['reveal_location_value'])
# Minted value is definitely equals to the sat value.
atomical_value = sat_value
# Save the initial location to have the atomical located there
tx_numb = pack_le_uint64(mint_info['reveal_location_tx_num'])[:TXNUM_LEN]
self.put_atomicals_utxo(mint_info['reveal_location'], mint_info['id'], mint_info['reveal_location_hashX'] + mint_info['reveal_location_scripthash'] + value_sats + pack_le_uint16(0) + tx_numb)
put_bytes: bytes = mint_info['reveal_location_hashX'] + mint_info['reveal_location_scripthash'] + sat_value + atomical_value + tx_numb
self.put_atomicals_utxo(mint_info['reveal_location'], mint_info['id'], put_bytes)
atomical_id = mint_info['id']
self.logger.debug(f'validate_and_create_nft_mint_utxo: atomical_id={location_id_bytes_to_compact(atomical_id)}, tx_hash={hash_to_hex_str(tx_hash)}, mint_info={mint_info}')
return True

# Validate the parameters for a FT
def validate_and_create_ft_mint_utxo(self, mint_info, tx_hash):
self.logger.debug(f'validate_and_create_ft_mint_utxo: tx_hash={hash_to_hex_str(tx_hash)}')
value_sats = pack_le_uint64(mint_info['reveal_location_value'])
sat_value = pack_le_uint64(mint_info['reveal_location_value'])
# Minted value is definitely equals to the sat value.
atomical_value = sat_value
# Save the initial location to have the atomical located there
if mint_info['subtype'] != 'decentralized':
tx_numb = pack_le_uint64(mint_info['reveal_location_tx_num'])[:TXNUM_LEN]
self.put_atomicals_utxo(mint_info['reveal_location'], mint_info['id'], mint_info['reveal_location_hashX'] + mint_info['reveal_location_scripthash'] + value_sats + pack_le_uint16(0) + tx_numb)
put_bytes: bytes = mint_info['reveal_location_hashX'] + mint_info['reveal_location_scripthash'] + sat_value + atomical_value + tx_numb
self.put_atomicals_utxo(mint_info['reveal_location'], mint_info['id'], put_bytes)
subtype = mint_info['subtype']
atomical_id = mint_info['id']
self.logger.debug(f'validate_and_create_ft_mint_utxo: subtype={subtype}, atomical_id={location_id_bytes_to_compact(atomical_id)}, tx_hash={hash_to_hex_str(tx_hash)}')
Expand Down Expand Up @@ -1659,26 +1665,27 @@ def put_or_delete_event_updates_if_found(self, operations_found_at_inputs, atomi
if is_event_operation(operations_found_at_inputs):
# Only allow an event to be posted to the first FT in the list, sorted
output_idx_le = pack_le_uint32(0) # Always save to 0th location
location = tx_hash + output_idx_le
txout = tx.outputs[0]
scripthash = double_sha256(txout.pk_script)
hashX = self.coin.hashX_from_script(txout.pk_script)
value_sats = pack_le_uint64(txout.value)
# location = tx_hash + output_idx_le
# txout = tx.outputs[0]
# scripthash = double_sha256(txout.pk_script)
# hashX = self.coin.hashX_from_script(txout.pk_script)
# sat_value = pack_le_uint64(txout.value)
self.put_or_delete_state_updates(operations_found_at_inputs, atomical_id, tx_num, tx_hash, output_idx_le, height, 1, False)

def build_put_atomicals_utxo(self, atomical_id, tx_hash, tx, tx_num, out_idx, exponent):
def build_put_atomicals_utxo(self, atomical_id, tx_hash, tx, tx_num, out_idx, atomical_value):
output_idx_le = pack_le_uint32(out_idx)
location = tx_hash + output_idx_le
txout = tx.outputs[out_idx]
scripthash = double_sha256(txout.pk_script)
hashX = self.coin.hashX_from_script(txout.pk_script)
value_sats = pack_le_uint64(txout.value)
sat_value = pack_le_uint64(txout.value)
atomical_value = pack_le_uint64(atomical_value)
put_general_data = self.general_data_cache.__setitem__
put_general_data(b'po' + location, txout.pk_script)
tx_numb = pack_le_uint64(tx_num)[:TXNUM_LEN]
self.put_atomicals_utxo(location, atomical_id, hashX + scripthash + value_sats + pack_le_uint16(exponent) + tx_numb)
self.put_atomicals_utxo(location, atomical_id, hashX + scripthash + sat_value + atomical_value + tx_numb)


def put_nft_outputs_by_blueprint(self, nft_blueprint, operations_found_at_inputs, tx_hash, tx, tx_num, height):
put_general_data = self.general_data_cache.__setitem__
self.logger.debug(f'nft_blueprint={nft_blueprint}')
Expand All @@ -1688,7 +1695,9 @@ def put_nft_outputs_by_blueprint(self, nft_blueprint, operations_found_at_inputs
txout = tx.outputs[output_idx]
scripthash = double_sha256(txout.pk_script)
hashX = self.coin.hashX_from_script(txout.pk_script)
value_sats = pack_le_uint64(txout.value)
sat_value = pack_le_uint64(txout.value)
# NFT value is definitely equals to the sat value.
atomical_value = sat_value
put_general_data(b'po' + location, txout.pk_script)
for atomical_id, atomical_info in value_info['atomicals'].items():
# Only allow state or event updates if it is not immutable
Expand All @@ -1712,14 +1721,15 @@ def put_nft_outputs_by_blueprint(self, nft_blueprint, operations_found_at_inputs
continue
# Only advance the UTXO if it was not sealed
tx_numb = pack_le_uint64(tx_num)[:TXNUM_LEN]
self.put_atomicals_utxo(location, atomical_id, hashX + scripthash + value_sats + pack_le_uint16(0) + tx_numb)
put_bytes: bytes = hashX + scripthash + sat_value + atomical_value + tx_numb
self.put_atomicals_utxo(location, atomical_id, put_bytes)

def put_ft_outputs_by_blueprint(self, ft_blueprint, operations_found_at_inputs, tx_hash, tx, tx_num, height):
for output_idx, value_info in ft_blueprint.outputs.items():
for atomical_id, atomical_transfer_info in value_info['atomicals'].items():
exponent = atomical_transfer_info.exponent
atomical_value = atomical_transfer_info.atomical_value
self.logger.debug(f'atomical_transfer_info={atomical_transfer_info}')
self.build_put_atomicals_utxo(atomical_id, tx_hash, tx, tx_num, output_idx, exponent)
self.build_put_atomicals_utxo(atomical_id, tx_hash, tx, tx_num, output_idx, atomical_value)
# Only allow an event to be posted to the first FT in the list, sorted
if ft_blueprint.first_atomical_id:
if operations_found_at_inputs:
Expand All @@ -1734,7 +1744,7 @@ def put_ft_outputs_by_blueprint(self, ft_blueprint, operations_found_at_inputs,
# Apply the rules to color the outputs of the atomicals
def color_atomicals_outputs(self, operations_found_at_inputs, atomicals_spent_at_inputs, tx, tx_hash, tx_num, height):
# Build the "blueprint" for how to assign all atomicals
blueprint_builder = AtomicalsTransferBlueprintBuilder(self.logger, atomicals_spent_at_inputs, operations_found_at_inputs, tx_hash, tx, self.get_atomicals_id_mint_info, self.is_dmint_activated(height))
blueprint_builder = AtomicalsTransferBlueprintBuilder(self.logger, atomicals_spent_at_inputs, operations_found_at_inputs, tx_hash, tx, self.get_atomicals_id_mint_info, self.is_dmint_activated(height), self.is_split_activated(height))

nft_output_blueprint = blueprint_builder.get_nft_output_blueprint()
if nft_output_blueprint and len(nft_output_blueprint.outputs):
Expand Down Expand Up @@ -2703,7 +2713,9 @@ def create_or_delete_decentralized_mint_output(self, atomicals_operations_found_
txout = tx.outputs[expected_output_index]
scripthash = double_sha256(txout.pk_script)
hashX = self.coin.hashX_from_script(txout.pk_script)
value_sats = pack_le_uint64(txout.value)
sat_value = pack_le_uint64(txout.value)
# Minted value is definitely equals to the sat value.
atomical_value = sat_value
# Mint is valid and active if the value is what is expected
if mint_amount == txout.value:
# Count the number of existing b'gi' entries and ensure it is strictly less than max_mints
Expand Down Expand Up @@ -2787,8 +2799,9 @@ def create_or_delete_decentralized_mint_output(self, atomicals_operations_found_
put_general_data = self.general_data_cache.__setitem__
put_general_data(the_key, txout.pk_script)
tx_numb = pack_le_uint64(tx_num)[:TXNUM_LEN]
self.put_atomicals_utxo(location, dmt_mint_atomical_id, hashX + scripthash + value_sats + pack_le_uint16(0) + tx_numb)
self.put_decentralized_mint_data(dmt_mint_atomical_id, location, scripthash + value_sats)
put_bytes: bytes = hashX + scripthash + sat_value + atomical_value + tx_numb
self.put_atomicals_utxo(location, dmt_mint_atomical_id, put_bytes)
self.put_decentralized_mint_data(dmt_mint_atomical_id, location, scripthash + sat_value)
self.logger.debug(f'create_or_delete_decentralized_mint_outputs found valid request in {hash_to_hex_str(tx_hash)} for {ticker}. Granting and creating decentralized mint...')
self.put_op_data(tx_num, tx_hash, "mint-dft")
return dmt_mint_atomical_id
Expand All @@ -2814,7 +2827,14 @@ def is_dmint_activated(self, height):
def is_density_activated(self, height):
if height >= self.coin.ATOMICALS_ACTIVATION_HEIGHT_DENSITY:
return True
return False
return False

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

# Builds a map of the atomicals spent at a tx
# It uses the spend_atomicals_utxo method but with live_run == False
Expand Down Expand Up @@ -3142,7 +3162,7 @@ def create_or_delete_subname_payment_output_if_valid(self, tx_hash, tx, tx_num,
return None, False

# Rebuild the blueprint builder here
blueprint_builder = AtomicalsTransferBlueprintBuilder(self.logger, atomicals_spent_at_inputs, operations_found_at_inputs, tx_hash, tx, self.get_atomicals_id_mint_info, self.is_dmint_activated(height))
blueprint_builder = AtomicalsTransferBlueprintBuilder(self.logger, atomicals_spent_at_inputs, operations_found_at_inputs, tx_hash, tx, self.get_atomicals_id_mint_info, self.is_dmint_activated(height), self.is_split_activated(height))
if blueprint_builder.is_split_operation():
self.logger.warning(f'create_or_delete_subname_payment_output_if_valid: invalid payment split op found tx_hash={hash_to_hex_str(tx_hash)}')
return tx_hash, False
Expand Down Expand Up @@ -3553,7 +3573,7 @@ def backup_txs(
'''

def spend_utxo(self, tx_hash: bytes, tx_idx: int) -> bytes:
'''Spend a UTXO and return (hashX + tx_num + value_sats).
'''Spend a UTXO and return (hashX + tx_num + sat_value).

If the UTXO is not in the cache it must be on disk. We store
all UTXOs so not finding one indicates a logic error or DB
Expand Down
Loading
Loading