Skip to content

Commit

Permalink
add correctly remove absorbed atomicals
Browse files Browse the repository at this point in the history
  • Loading branch information
jj committed Jul 29, 2024
1 parent c2b3626 commit 65777fc
Show file tree
Hide file tree
Showing 2 changed files with 38 additions and 27 deletions.
25 changes: 20 additions & 5 deletions electrumx/lib/avm/avm.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,23 @@
from electrumx.lib.atomicals_blueprint_builder import AtomicalsTransferBlueprintBuilder
from cbor2 import loads

# Updates the atomicals_spent_at_inputs structure for any consumed atomicals via either deploy or call
def remove_consumed_atomicals(ft_adds, nft_puts, atomicals_spent_at_inputs):
print(f'ft_adds={ft_adds}')
print(f'nft_puts={nft_puts}')
print(f'atomicals_spent_at_inputs={atomicals_spent_at_inputs}')
# Clean up and remove the atomical entry if it was consumed
for idx, atomical_entry_list in atomicals_spent_at_inputs.items():
i = 0
for atomical_entry in atomical_entry_list:
atomical_id = atomical_entry['atomical_id']
# It was consumed therefore remove it from the input
if ft_adds.get(atomical_id) or nft_puts.get(atomical_id):
del atomical_entry[i]
break
i += 1
return

class CallCommandResult:
def __init__(self, success, reactor_context, error=None):
self.success = success
Expand Down Expand Up @@ -102,9 +119,7 @@ def execute(self):
print(f'updated_reactor_state.ft_balances={loads(updated_reactor_state.ft_balances)}')
print(f'updated_reactor_state.nft_balances={loads(updated_reactor_state.nft_balances)}')
if updated_reactor_state:
# Todo: Only clear off the atomicals here that were actually accepted by the script
self.request_tx_context.atomicals_spent_at_inputs = {}
# Todo: Color the FT/NFT outputs
remove_consumed_atomicals(updated_reactor_state.ft_adds, updated_reactor_state.nft_puts, self.atomicals_spent_at_inputs)
return CallCommandResult(True, updated_reactor_state)
except AtomicalConsensusExecutionError as ex:
print(f'AtomicalConsensusExecutionError ex={ex}')
Expand Down Expand Up @@ -183,13 +198,13 @@ def execute(self):
loads(updated_reactor_state.ft_balances)
loads(updated_reactor_state.nft_balances)
if updated_reactor_state:
self.request_tx_context.atomicals_spent_at_inputs = {}
remove_consumed_atomicals(updated_reactor_state.ft_adds, updated_reactor_state.nft_puts, self.atomicals_spent_at_inputs)
return DeployCommandResult(True, updated_reactor_state)
except AtomicalConsensusExecutionError as ex:
print(f'AtomicalConsensusExecutionError ex={ex}')
return DeployCommandResult(False, None, ex)
raise ValueError(f'Critical call error')

class AVMFactory:
def __init__(self, logger, get_atomicals_id_mint_info, blockchain_context: RequestBlockchainContext, protocol_mint_data):
self.logger = logger
Expand Down
40 changes: 18 additions & 22 deletions electrumx/server/block_processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -1200,7 +1200,6 @@ def create_or_delete_protocol_entry_if_requested(self, mint_info, height, Delete
self.put_name_element_template(b'pr', b'', request_protocol, mint_info['commit_tx_num'], mint_info['id'], self.protocol_data_cache)
return True

# mint_info, tx_hash, tx, header, protocol_mint_data, height, Delete):
def create_or_delete_contract_entry_if_requested(self, protocol_atomical_id, mint_info, tx_hash, tx, header, protocol_mint_data, operations_found_at_inputs, atomicals_spent_at_inputs, height, Delete=False):
request_contract = mint_info.get('$request_contract')
if not request_contract:
Expand All @@ -1217,9 +1216,11 @@ def create_or_delete_contract_entry_if_requested(self, protocol_atomical_id, min
return False
if Delete:
self.delete_name_element_template(b'cr', b'', request_contract, mint_info['commit_tx_num'], mint_info['id'], self.reactor_data_cache)
# We can use dummy reactor context for delete case
self.put_or_delete_reactor_states(mint_info['id'], None, height, True)
else:
#
# General blockchain context such as headers and height
# Add general blockchain context such as headers and height
#
headers = {}
# Todo add the last N=1000 headers potentially
Expand Down Expand Up @@ -1504,21 +1505,16 @@ def get_latest_reactor_states(self, reactor_id):
return latest_height_db, latest_state_db
return None, None

def put_or_delete_reactor_states(self, reactor_id, reactor_context: ReactorContext, height, Delete):
def put_or_delete_reactor_states(self, reactor_id, reactor_context, height, Delete):
self.logger.info(f'put_or_delete_reactor_states reactor_id={location_id_bytes_to_compact(reactor_id)}')
found_reactor_record = self.reactor_states_cache.get(reactor_id)

if not reactor_context.state or not reactor_context.ft_balances or not reactor_context.nft_balances:
raise IndexError('Developer error')

if not Delete:
if not found_reactor_record:
self.reactor_states_cache[reactor_id] = {}

self.reactor_states_cache[reactor_id][height] = pickle.dumps(reactor_context)
else:
if found_reactor_record:
self.reactor_states_cache[reactor_id].pop(height, None)
del self.reactor_states_cache[reactor_id][height]
db_key = b'rcs' + reactor_id + pack_be_uint32(height)
self.db_deletes.append(db_key)

Expand Down Expand Up @@ -1697,10 +1693,6 @@ def create_or_delete_atomical(self, operations_found_at_inputs, atomicals_spent_
return None

if not Delete:

# Absorbs all the atomicals tokens because the only way have gotten this far is if a payable method was called
atomicals_spent_at_inputs.clear()

self.logger.info(f'mint-reactor: {hash_to_hex_str(tx_hash)}')
self.put_op_data(tx_num, tx_hash, "mint-reactor")

Expand Down Expand Up @@ -3341,20 +3333,12 @@ def advance_txs(
reveal_location_index = atomicals_operations_found_at_inputs['reveal_location_index']
self.logger.debug(f'advance_txs: atomicals_operations_found_at_inputs operation_found={operation_found}, operation_input_index={operation_input_index}, size_payload={size_payload}, tx_hash={hash_to_hex_str(tx_hash)}, commit_txid={hash_to_hex_str(commit_txid)}, commit_index={commit_index}, reveal_location_txid={hash_to_hex_str(reveal_location_txid)}, reveal_location_index={reveal_location_index}')

# Todo ensure this call modifies the atomicals_spent_at_inputs if contract absorbs NFT/FT
# This call modifies the atomicals_spent_at_inputs if contract absorbs NFT/FT
request_id = self.create_or_delete_call(atomicals_operations_found_at_inputs, atomicals_spent_at_inputs, tx, tx_hash, tx_num, header, height, False)
if request_id:
already_found_valid_operation = True
has_at_least_one_valid_atomicals_operation = True

# Color the outputs of any transferred NFT/FT atomicals according to the rules
blueprint_builder = self.color_atomicals_outputs(atomicals_operations_found_at_inputs, atomicals_spent_at_inputs, tx, tx_hash, tx_num, height)
for atomical_id in blueprint_builder.get_atomical_ids_spent():
has_at_least_one_valid_atomicals_operation = True
self.logger.debug(f'advance_txs: color_atomicals_outputs atomical_ids_transferred. atomical_id={atomical_id.hex()}, tx_hash={hash_to_hex_str(tx_hash)}')
# Double hash the atomical_id to add it to the history to leverage the existing history db for all operations involving the atomical
append_hashX(double_sha256(atomical_id))

# Track whether we encountered a valid operation so we can skip other steps in the processing pipeline for efficiency
already_found_valid_operation = False

Expand All @@ -3373,6 +3357,7 @@ def advance_txs(

# Create NFT/FT atomicals if it is defined in the tx
if not already_found_valid_operation:
# An AVM deploy call modifies the atomicals_spent_at_inputs if contract absorbs NFT/FT
created_atomical_id = self.create_or_delete_atomical(atomicals_operations_found_at_inputs, atomicals_spent_at_inputs, header, height, tx_num, atomical_num, tx, tx_hash, False)
if created_atomical_id:
already_found_valid_operation = True
Expand All @@ -3382,6 +3367,17 @@ def advance_txs(
append_hashX(double_sha256(created_atomical_id))
self.logger.debug(f'advance_txs: create_or_delete_atomical created_atomical_id atomical_id={created_atomical_id.hex()}, tx_hash={hash_to_hex_str(tx_hash)}')

# Color the outputs of any transferred NFT/FT atomicals according to the rules
# Note: this was moved AFTER create_or_delete_atomical because we must account for clearing entries in atomicals_spent_at_inputs if
# the deploy call absorbed atomicals on reactor contract mint.
# The atomicals not absorbed by the call or deploy, just get transferred the normal way according to the predefined rules
blueprint_builder = self.color_atomicals_outputs(atomicals_operations_found_at_inputs, atomicals_spent_at_inputs, tx, tx_hash, tx_num, height)
for atomical_id in blueprint_builder.get_atomical_ids_spent():
has_at_least_one_valid_atomicals_operation = True
self.logger.debug(f'advance_txs: color_atomicals_outputs atomical_ids_transferred. atomical_id={atomical_id.hex()}, tx_hash={hash_to_hex_str(tx_hash)}')
# Double hash the atomical_id to add it to the history to leverage the existing history db for all operations involving the atomical
append_hashX(double_sha256(atomical_id))

# Check if there were any regular 'dat' files definitions
if not already_found_valid_operation:
if self.create_or_delete_data_location(tx_hash, atomicals_operations_found_at_inputs):
Expand Down

0 comments on commit 65777fc

Please sign in to comment.