Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions hathor/builder/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -590,10 +590,12 @@ def _get_or_create_verification_service(self) -> VerificationService:
settings = self._get_or_create_settings()
verifiers = self._get_or_create_vertex_verifiers()
storage = self._get_or_create_tx_storage()
nc_storage_factory = self._get_or_create_nc_storage_factory()
self._verification_service = VerificationService(
settings=settings,
verifiers=verifiers,
tx_storage=storage,
nc_storage_factory=nc_storage_factory,
)

return self._verification_service
Expand Down
1 change: 1 addition & 0 deletions hathor/builder/cli_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,7 @@ def create_manager(self, reactor: Reactor) -> HathorManager:
settings=settings,
verifiers=vertex_verifiers,
tx_storage=tx_storage,
nc_storage_factory=self.nc_storage_factory,
)

cpu_mining_service = CpuMiningService()
Expand Down
2 changes: 1 addition & 1 deletion hathor/cli/mining.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ def execute(args: Namespace) -> None:
from hathor.verification.vertex_verifiers import VertexVerifiers
settings = get_global_settings()
daa = DifficultyAdjustmentAlgorithm(settings=settings)
verification_params = VerificationParams.default_for_mempool()
verification_params = VerificationParams(nc_block_root_id=None, enable_checkdatasig_count=True)
verifiers = VertexVerifiers.create_defaults(
reactor=Mock(),
settings=settings,
Expand Down
26 changes: 25 additions & 1 deletion hathor/consensus/block_consensus.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
from hathor.consensus.context import ReorgInfo
from hathor.feature_activation.feature import Feature
from hathor.transaction import BaseTransaction, Block, Transaction
from hathor.transaction.exceptions import TokenNotFound
from hathor.transaction.nc_execution_state import NCExecutionState
from hathor.transaction.types import MetaNCCallRecord
from hathor.util import classproperty
Expand All @@ -37,6 +38,7 @@
from hathor.nanocontracts.nc_exec_logs import NCLogStorage
from hathor.nanocontracts.runner import Runner
from hathor.nanocontracts.runner.runner import RunnerFactory
from hathor.nanocontracts.storage import NCBlockStorage

logger = get_logger()

Expand Down Expand Up @@ -236,8 +238,17 @@ def _nc_execute_calls(self, block: Block, *, is_reorg: bool) -> None:

runner = self._runner_factory.create(block_storage=block_storage, seed=seed_hasher.digest())
exception_and_tb: tuple[NCFail, str] | None = None
token_dict = tx.get_complete_token_info(block_storage)
should_verify_sum_after_execution = any(token_info.version is None for token_info in token_dict.values())

try:
runner.execute_from_tx(tx)

# after the execution we have the latest state in the storage
# and at this point no tokens pending creation
if should_verify_sum_after_execution:
self._verify_sum_after_execution(tx, block_storage)

except NCFail as e:
kwargs: dict[str, Any] = {}
if tx.name:
Expand Down Expand Up @@ -301,8 +312,21 @@ def _nc_execute_calls(self, block: Block, *, is_reorg: bool) -> None:
case _: # pragma: no cover
assert_never(tx_meta.nc_execution)

def _verify_sum_after_execution(self, tx: Transaction, block_storage: NCBlockStorage) -> None:
from hathor import NCFail
from hathor.verification.transaction_verifier import TransactionVerifier
try:
token_dict = tx.get_complete_token_info(block_storage)
TransactionVerifier.verify_sum(self._settings, token_dict)
except TokenNotFound as e:
# At this point, any nonexistent token would have made a prior validation fail. For example, if there
# was a withdrawal of a nonexistent token, it would have failed in the balance validation before.
raise AssertionError from e
except Exception as e:
raise NCFail from e

def nc_update_metadata(self, tx: Transaction, runner: 'Runner') -> None:
from hathor.nanocontracts.runner.types import CallType
from hathor.nanocontracts.runner.call_info import CallType

meta = tx.get_metadata()
assert meta.nc_execution == NCExecutionState.SUCCESS
Expand Down
8 changes: 7 additions & 1 deletion hathor/dag_builder/artifacts.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,13 @@ def propagate_with(
if new_relayed_vertex:
assert manager.vertex_handler.on_new_relayed_vertex(vertex)
else:
params = VerificationParams(enable_checkdatasig_count=True, enable_nano=True)
best_block = manager.tx_storage.get_best_block()
best_block_meta = best_block.get_metadata()
params = VerificationParams(
enable_checkdatasig_count=True,
enable_nano=True,
nc_block_root_id=best_block_meta.nc_block_root_id,
)
assert manager.vertex_handler._old_on_new_vertex(vertex, params)
except Exception as e:
raise Exception(f'failed on_new_tx({node.name})') from e
Expand Down
58 changes: 27 additions & 31 deletions hathor/indexes/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -216,11 +216,12 @@ def handle_contract_execution(self, tx: BaseTransaction) -> None:
Update indexes according to a Nano Contract execution.
Must be called only once for each time a contract is executed.
"""
from hathor.nanocontracts.runner.types import (
from hathor.nanocontracts.runner.index_records import (
CreateContractRecord,
CreateTokenRecord,
NCIndexUpdateRecord,
SyscallCreateContractRecord,
SyscallUpdateTokenRecord,
UpdateAuthoritiesRecord,
UpdateTokenBalanceRecord,
)
from hathor.nanocontracts.types import ContractId
from hathor.transaction.nc_execution_state import NCExecutionState
Expand All @@ -244,7 +245,7 @@ def handle_contract_execution(self, tx: BaseTransaction) -> None:
created_contracts: set[ContractId] = set()
for record in index_records:
match record:
case SyscallCreateContractRecord(blueprint_id=blueprint_id, contract_id=contract_id):
case CreateContractRecord(blueprint_id=blueprint_id, contract_id=contract_id):
assert contract_id not in created_contracts, f'contract {contract_id.hex()} created multiple times'
assert contract_id != first_call.contract_id, (
f'contract {contract_id.hex()} cannot make a syscall to create itself'
Expand All @@ -259,26 +260,20 @@ def handle_contract_execution(self, tx: BaseTransaction) -> None:
if self.blueprint_history:
self.blueprint_history.add_single_key(blueprint_id, tx)

case SyscallUpdateTokenRecord():
case CreateTokenRecord():
if self.tokens:
self.tokens.create_token_info_from_contract(
token_uid=record.token_uid,
name=record.token_name,
symbol=record.token_symbol,
version=record.token_version,
total=record.amount,
)

case UpdateTokenBalanceRecord():
# Minted/melted tokens are added/removed to/from the tokens index,
# and the respective destroyed/created HTR too.
if self.tokens:
try:
self.tokens.get_token_info(record.token_uid)
except KeyError:
# If the token doesn't exist in the index yet, it must be a token creation syscall.
from hathor.nanocontracts.runner.types import IndexUpdateRecordType
assert record.type == IndexUpdateRecordType.CREATE_TOKEN, record.type
assert record.token_name is not None and record.token_symbol is not None
assert record.token_version is not None

self.tokens.create_token_info_from_contract(
token_uid=record.token_uid,
name=record.token_name,
symbol=record.token_symbol,
version=record.token_version
)

self.tokens.add_to_total(record.token_uid, record.amount)

case UpdateAuthoritiesRecord():
Expand All @@ -293,11 +288,12 @@ def handle_contract_unexecution(self, tx: BaseTransaction) -> None:
Update indexes according to a Nano Contract unexecution, which happens when a reorg unconfirms a nano tx.
Must be called only once for each time a contract is unexecuted.
"""
from hathor.nanocontracts.runner.types import (
from hathor.nanocontracts.runner.index_records import (
CreateContractRecord,
CreateTokenRecord,
NCIndexUpdateRecord,
SyscallCreateContractRecord,
SyscallUpdateTokenRecord,
UpdateAuthoritiesRecord,
UpdateTokenBalanceRecord,
)
from hathor.nanocontracts.types import NC_INITIALIZE_METHOD, ContractId

Expand All @@ -314,13 +310,13 @@ def handle_contract_unexecution(self, tx: BaseTransaction) -> None:
if self.nc_history and call.contract_id != first_call.contract_id:
self.nc_history.remove_single_key(call.contract_id, tx)

# Accumulate all syscalls.
# Accumulate all index update records.
records.extend(call.index_updates)

created_contracts: set[ContractId] = set()
for record in records:
match record:
case SyscallCreateContractRecord(blueprint_id=blueprint_id, contract_id=contract_id):
case CreateContractRecord(blueprint_id=blueprint_id, contract_id=contract_id):
assert contract_id not in created_contracts, f'contract {contract_id.hex()} created multiple times'
assert contract_id != first_call.contract_id, (
f'contract {contract_id.hex()} cannot make a syscall to create itself'
Expand All @@ -338,13 +334,13 @@ def handle_contract_unexecution(self, tx: BaseTransaction) -> None:
if self.blueprint_history:
self.blueprint_history.remove_single_key(blueprint_id, tx)

case SyscallUpdateTokenRecord():
case CreateTokenRecord():
if self.tokens:
self.tokens.add_to_total(record.token_uid, -record.amount)
self.tokens.destroy_token(record.token_uid)

from hathor.nanocontracts.runner.types import IndexUpdateRecordType
if record.type == IndexUpdateRecordType.CREATE_TOKEN:
self.tokens.destroy_token(record.token_uid)
case UpdateTokenBalanceRecord():
if self.tokens:
self.tokens.add_to_total(record.token_uid, -record.amount)

case UpdateAuthoritiesRecord():
if self.tokens:
Expand Down
12 changes: 6 additions & 6 deletions hathor/indexes/rocksdb_tokens_index.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
to_internal_token_uid,
)
from hathor.indexes.tokens_index import TokenIndexInfo, TokensIndex, TokenUtxoInfo
from hathor.nanocontracts.runner.types import UpdateAuthoritiesRecord, UpdateAuthoritiesRecordType
from hathor.nanocontracts.runner.index_records import IndexRecordType, UpdateAuthoritiesRecord
from hathor.nanocontracts.types import (
NCAcquireAuthorityAction,
NCDepositAction,
Expand Down Expand Up @@ -270,7 +270,7 @@ def create_token_info_from_contract(
name: str,
symbol: str,
version: TokenVersion,
total: int = 0,
total: int,
) -> None:
self.create_token_info(
token_uid=token_uid,
Expand Down Expand Up @@ -515,13 +515,13 @@ def update_authorities_from_contract(self, record: UpdateAuthoritiesRecord, undo
dict_info = self._get_value_info(record.token_uid)

increment: int
match record.sub_type:
case UpdateAuthoritiesRecordType.GRANT:
match record.type:
case IndexRecordType.GRANT_AUTHORITIES:
increment = 1
case UpdateAuthoritiesRecordType.REVOKE:
case IndexRecordType.REVOKE_AUTHORITIES:
increment = -1
case _:
assert_never(record.sub_type)
assert_never(record.type)

if undo:
increment *= -1
Expand Down
4 changes: 2 additions & 2 deletions hathor/indexes/tokens_index.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
from hathor.transaction.token_info import TokenVersion

if TYPE_CHECKING:
from hathor.nanocontracts.runner.types import UpdateAuthoritiesRecord
from hathor.nanocontracts.runner.index_records import UpdateAuthoritiesRecord

SCOPE = Scope(
include_blocks=False,
Expand Down Expand Up @@ -157,7 +157,7 @@ def create_token_info_from_contract(
name: str,
symbol: str,
version: TokenVersion,
total: int = 0,
total: int,
) -> None:
"""Create a token info for a new token created in a contract."""
raise NotImplementedError
Expand Down
17 changes: 11 additions & 6 deletions hathor/nanocontracts/balance_rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,7 @@
NCGrantAuthorityAction,
NCWithdrawalAction,
)
from hathor.transaction.token_info import TokenInfoDict
from hathor.transaction.transaction import TokenInfo
from hathor.transaction.token_info import TokenInfoDict, TokenVersion

T = TypeVar('T', bound=BaseAction)

Expand Down Expand Up @@ -101,10 +100,13 @@ class _DepositRules(BalanceRules[NCDepositAction]):

@override
def verification_rule(self, token_dict: TokenInfoDict) -> None:
token_info = token_dict.get(self.action.token_uid, TokenInfo.get_default())
token_info = token_dict[self.action.token_uid]
token_info.amount = token_info.amount + self.action.amount
token_dict[self.action.token_uid] = token_info

if token_info.version == TokenVersion.FEE:
token_info.chargeable_outputs += 1

@override
def nc_callee_execution_rule(self, callee_changes_tracker: NCChangesTracker) -> None:
callee_changes_tracker.add_balance(self.action.token_uid, self.action.amount)
Expand All @@ -125,10 +127,13 @@ class _WithdrawalRules(BalanceRules[NCWithdrawalAction]):

@override
def verification_rule(self, token_dict: TokenInfoDict) -> None:
token_info = token_dict.get(self.action.token_uid, TokenInfo.get_default())
token_info = token_dict[self.action.token_uid]
token_info.amount = token_info.amount - self.action.amount
token_dict[self.action.token_uid] = token_info

if token_info.version == TokenVersion.FEE:
token_info.chargeable_inputs += 1

@override
def nc_callee_execution_rule(self, callee_changes_tracker: NCChangesTracker) -> None:
callee_changes_tracker.add_balance(self.action.token_uid, -self.action.amount)
Expand All @@ -150,7 +155,7 @@ class _GrantAuthorityRules(BalanceRules[NCGrantAuthorityAction]):
@override
def verification_rule(self, token_dict: TokenInfoDict) -> None:
assert self.action.token_uid != HATHOR_TOKEN_UID
token_info = token_dict.get(self.action.token_uid, TokenInfo.get_default())
token_info = token_dict[self.action.token_uid]
if self.action.mint and not token_info.can_mint:
raise NCInvalidAction(
f'{self.action.name} token {self.action.token_uid.hex()} requires mint, but no input has it'
Expand Down Expand Up @@ -202,7 +207,7 @@ class _AcquireAuthorityRules(BalanceRules[NCAcquireAuthorityAction]):
@override
def verification_rule(self, token_dict: TokenInfoDict) -> None:
assert self.action.token_uid != HATHOR_TOKEN_UID
token_info = token_dict.get(self.action.token_uid, TokenInfo.get_default())
token_info = token_dict[self.action.token_uid]
token_info.can_mint = token_info.can_mint or self.action.mint
token_info.can_melt = token_info.can_melt or self.action.melt
token_dict[self.action.token_uid] = token_info
Expand Down
2 changes: 1 addition & 1 deletion hathor/nanocontracts/blueprint_env.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
from typing import TYPE_CHECKING, Any, Collection, Sequence, TypeAlias, final

from hathor.conf.settings import HATHOR_TOKEN_UID
from hathor.nanocontracts.storage import NCContractStorage
from hathor.nanocontracts.types import Amount, BlueprintId, ContractId, NCAction, NCFee, TokenUid

if TYPE_CHECKING:
Expand All @@ -27,6 +26,7 @@
from hathor.nanocontracts.proxy_accessor import ProxyAccessor
from hathor.nanocontracts.rng import NanoRNG
from hathor.nanocontracts.runner import Runner
from hathor.nanocontracts.storage import NCContractStorage


NCAttrCache: TypeAlias = dict[bytes, Any] | None
Expand Down
26 changes: 26 additions & 0 deletions hathor/nanocontracts/nc_types/token_version_nc_type.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Copyright 2025 Hathor Labs
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from typing_extensions import override

from hathor.nanocontracts.nc_types.sized_int_nc_type import Uint8NCType
from hathor.serialization import Deserializer
from hathor.transaction.token_info import TokenVersion


class TokenVersionNCType(Uint8NCType):
@override
def _deserialize(self, deserializer: Deserializer, /) -> TokenVersion:
value = super()._deserialize(deserializer)
return TokenVersion(value)
Loading
Loading