Skip to content
Merged
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 @@ -292,6 +292,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
24 changes: 24 additions & 0 deletions 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,6 +312,19 @@ 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

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
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]
Comment thread
msbrogli marked this conversation as resolved.
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
Comment thread
glevco marked this conversation as resolved.

@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())
Comment thread
msbrogli marked this conversation as resolved.
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
Comment thread
glevco marked this conversation as resolved.

@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]
Comment thread
raul-oliveira marked this conversation as resolved.
Comment thread
msbrogli marked this conversation as resolved.
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)
4 changes: 2 additions & 2 deletions hathor/nanocontracts/storage/block_storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

from hathor.nanocontracts.exception import NanoContractDoesNotExist
from hathor.nanocontracts.nc_types.dataclass_nc_type import make_dataclass_nc_type
from hathor.nanocontracts.nc_types.sized_int_nc_type import Uint8NCType
from hathor.nanocontracts.nc_types.token_version_nc_type import TokenVersionNCType
from hathor.nanocontracts.storage.contract_storage import NCContractStorage
from hathor.nanocontracts.storage.patricia_trie import NodeId, PatriciaTrie
from hathor.nanocontracts.storage.token_proxy import TokenProxy
Expand Down Expand Up @@ -64,7 +64,7 @@ class NCBlockStorage:
_TOKEN_DESCRIPTION_NC_TYPE = make_dataclass_nc_type(
TokenDescription,
extra_nc_types_map={
TokenVersion: Uint8NCType,
TokenVersion: TokenVersionNCType,
},
)

Expand Down
12 changes: 9 additions & 3 deletions hathor/p2p/sync_v2/transaction_streaming_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,17 @@ def __init__(self,
self.protocol = self.sync_agent.protocol
self.tx_storage = self.sync_agent.tx_storage
self.verification_service = self.protocol.node.verification_service
# XXX: since it's not straightforward to get the correct block, it's OK to just disable checkdatasig counting,

# XXX: Since it's not straightforward to get the correct block, it's OK to just disable checkdatasig counting,
# it will be correctly enabled when doing a full validation anyway.
self.verification_params = VerificationParams(enable_checkdatasig_count=False)
self.reactor = sync_agent.reactor
# We can also set the `nc_block_root_id` to `None` because we only call `verify_basic`,
# which doesn't need it.
self.verification_params = VerificationParams(
Comment thread
raul-oliveira marked this conversation as resolved.
enable_checkdatasig_count=False,
nc_block_root_id=None,
Comment thread
msbrogli marked this conversation as resolved.
)

self.reactor = sync_agent.reactor
self.log = logger.new(peer=self.protocol.get_short_peer_id())

# List of blocks from which we will receive transactions.
Expand Down
2 changes: 1 addition & 1 deletion hathor/transaction/base_transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ def is_transaction(self) -> bool:
raise NotImplementedError

def is_nano_contract(self) -> bool:
"""Return True if this transaction is a nano contract or not."""
"""Return whether this transaction is a nano contract."""
return False

def has_fees(self) -> bool:
Expand Down
4 changes: 4 additions & 0 deletions hathor/transaction/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -274,3 +274,7 @@ class FeeHeaderTokenNotFound(InvalidFeeHeader):

class InvalidFeeAmount(InvalidFeeHeader):
"""Invalid fee amount"""


class TokenNotFound(TxValidationError):
"""Token not found."""
5 changes: 4 additions & 1 deletion hathor/transaction/resources/create_tx.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,10 @@ def _verify_unsigned_skip_pow(self, tx: Transaction) -> None:
# need to run verify_inputs first to check if all inputs exist
verifiers.tx.verify_inputs(tx, skip_script=True)
verifiers.vertex.verify_parents(tx)
verifiers.tx.verify_sum(tx.get_complete_token_info())

best_block = self.manager.tx_storage.get_best_block()
block_storage = self.manager.get_nc_block_storage(best_block)
Comment thread
raul-oliveira marked this conversation as resolved.
verifiers.tx.verify_sum(self.manager._settings, tx.get_complete_token_info(block_storage))


CreateTxResource.openapi = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def skip_empty_db(self) -> bool:
return True

def get_db_name(self) -> str:
return 'nc_storage_compat1'
return 'nc_storage_compat2'

def run(self, storage: 'TransactionStorage') -> None:
raise Exception('Cannot migrate your database due to an incompatible change in the nanocontracts storage. '
Expand Down
4 changes: 2 additions & 2 deletions hathor/transaction/storage/transaction_storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
add_closest_ancestor_block,
change_score_acc_weight_metadata,
include_funds_for_first_block,
nc_storage_compat1,
nc_storage_compat2,
)
from hathor.transaction.storage.tx_allow_scope import TxAllowScope, tx_allow_context
from hathor.transaction.transaction import Transaction
Expand Down Expand Up @@ -104,7 +104,7 @@ class TransactionStorage(ABC):
change_score_acc_weight_metadata.Migration,
add_closest_ancestor_block.Migration,
include_funds_for_first_block.Migration,
nc_storage_compat1.Migration,
nc_storage_compat2.Migration,
]

_migrations: list[BaseMigration]
Expand Down
7 changes: 4 additions & 3 deletions hathor/transaction/token_creation_tx.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from typing_extensions import override

from hathor.conf.settings import HathorSettings
from hathor.nanocontracts.storage import NCBlockStorage
from hathor.transaction.base_transaction import TxInput, TxOutput, TxVersion
from hathor.transaction.storage import TransactionStorage # noqa: F401
from hathor.transaction.token_info import TokenInfo, TokenInfoDict, TokenVersion
Expand Down Expand Up @@ -250,10 +251,10 @@ def to_json_extended(self) -> dict[str, Any]:
return json

@override
def _get_token_info_from_inputs(self) -> TokenInfoDict:
token_dict = super()._get_token_info_from_inputs()
def _get_token_info_from_inputs(self, nc_block_storage: NCBlockStorage) -> TokenInfoDict:
token_dict = super()._get_token_info_from_inputs(nc_block_storage)

# we add the created token's info to token_dict, as the creation tx allows for mint/melt
token_dict[self.hash] = TokenInfo.get_default(version=self.token_version, can_mint=True, can_melt=True)
token_dict[self.hash] = TokenInfo(version=self.token_version, can_mint=True, can_melt=True)

return token_dict
Loading
Loading