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
5 changes: 3 additions & 2 deletions hathor/conf/mainnet.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
# limitations under the License.

from hathor.checkpoint import Checkpoint as cp
from hathor.conf.settings import HathorSettings, NanoContractsSetting
from hathor.conf.settings import FeatureSetting, HathorSettings
from hathor.feature_activation.feature import Feature
from hathor.feature_activation.model.criteria import Criteria
from hathor.feature_activation.settings import Settings as FeatureActivationSettings
Expand Down Expand Up @@ -213,7 +213,8 @@
'00004305882eb3eef6b45f025ff58eb7baa5ca35f7d6f42c8b085482b00474e6',
'000045ecbab77c9a8d819ff6d26893b9da2774eee5539f17d8fc2394f82b758e',
])),
ENABLE_NANO_CONTRACTS=NanoContractsSetting.FEATURE_ACTIVATION,
ENABLE_NANO_CONTRACTS=FeatureSetting.FEATURE_ACTIVATION,
ENABLE_FEE_BASED_TOKENS=FeatureSetting.DISABLED,
NC_ON_CHAIN_BLUEPRINT_ALLOWED_ADDRESSES=[
'HDkKGHwDHTuUGbhET73XdTJZkS8uU7PHf9',
'HUbxYhtqW8pdRCC2WngPxN7MB4SUMDPrrh',
Expand Down
17 changes: 10 additions & 7 deletions hathor/conf/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@


@unique
class NanoContractsSetting(StrEnum):
"""Enum to configure the state of the Nano Contracts feature."""
class FeatureSetting(StrEnum):
"""Enum to configure the state of a feature."""

# Completely disabled.
DISABLED = auto()
Expand All @@ -49,14 +49,14 @@ class NanoContractsSetting(StrEnum):

def __bool__(self) -> bool:
"""
>>> bool(NanoContractsSetting.DISABLED)
>>> bool(FeatureSetting.DISABLED)
False
>>> bool(NanoContractsSetting.ENABLED)
>>> bool(FeatureSetting.ENABLED)
True
>>> bool(NanoContractsSetting.FEATURE_ACTIVATION)
>>> bool(FeatureSetting.FEATURE_ACTIVATION)
True
"""
return self in (NanoContractsSetting.ENABLED, NanoContractsSetting.FEATURE_ACTIVATION)
return self in (FeatureSetting.ENABLED, FeatureSetting.FEATURE_ACTIVATION)


class HathorSettings(NamedTuple):
Expand Down Expand Up @@ -480,7 +480,10 @@ def GENESIS_TX2_TIMESTAMP(self) -> int:
MAX_UNVERIFIED_PEERS_PER_CONN: int = 100

# Used to enable nano contracts.
ENABLE_NANO_CONTRACTS: NanoContractsSetting = NanoContractsSetting.DISABLED
ENABLE_NANO_CONTRACTS: FeatureSetting = FeatureSetting.DISABLED

# Used to enable fee-based tokens.
ENABLE_FEE_BASED_TOKENS: FeatureSetting = FeatureSetting.DISABLED

# List of enabled blueprints.
BLUEPRINTS: dict[bytes, str] = {}
Expand Down
1 change: 1 addition & 0 deletions hathor/conf/unittests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ FEATURE_ACTIVATION:
default_threshold: 3

ENABLE_NANO_CONTRACTS: enabled
ENABLE_FEE_BASED_TOKENS: enabled

NC_ON_CHAIN_BLUEPRINT_ALLOWED_ADDRESSES:
# keypair wallet:
Expand Down
27 changes: 7 additions & 20 deletions hathor/consensus/block_consensus.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
from typing_extensions import assert_never

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
Expand Down Expand Up @@ -145,26 +144,11 @@ def _should_execute_nano(self, block: Block) -> bool:
"""
Determine whether we should proceed to execute Nano transactions while making the necessary initializations.
"""
from hathor.conf.settings import NanoContractsSetting
assert not block.is_genesis

match self._settings.ENABLE_NANO_CONTRACTS:
case NanoContractsSetting.ENABLED:
return True

case NanoContractsSetting.FEATURE_ACTIVATION:
parent = block.get_block_parent()
is_active_on_parent = self.feature_service.is_feature_active(
vertex=parent,
feature=Feature.NANO_CONTRACTS,
)
return is_active_on_parent

case NanoContractsSetting.DISABLED:
return False

case _: # pragma: no cover
assert_never(self._settings.ENABLE_NANO_CONTRACTS)
from hathor.feature_activation.utils import is_nano_active
parent = block.get_block_parent()
return is_nano_active(settings=self._settings, feature_service=self.feature_service, block=parent)

def _nc_execute_calls(self, block: Block, *, is_reorg: bool) -> None:
"""Internal method to execute the method calls for transactions confirmed by this block.
Expand Down Expand Up @@ -239,7 +223,10 @@ def _nc_execute_calls(self, block: Block, *, is_reorg: bool) -> None:
block_storage.set_address_seqnum(Address(nc_header.nc_address), nc_header.nc_seqnum)
continue

runner = self._runner_factory.create(block_storage=block_storage, seed=seed_hasher.digest())
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())
Expand Down
24 changes: 19 additions & 5 deletions hathor/consensus/consensus.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
from hathor.consensus.block_consensus import BlockConsensusAlgorithmFactory
from hathor.consensus.context import ConsensusAlgorithmContext
from hathor.consensus.transaction_consensus import TransactionConsensusAlgorithmFactory
from hathor.feature_activation.utils import is_fee_active
from hathor.profiler import get_cpu_profiler
from hathor.pubsub import HathorEvents, PubSubManager
from hathor.transaction import BaseTransaction, Transaction
Expand Down Expand Up @@ -360,6 +361,7 @@ def _compute_vertices_that_became_invalid(
lambda tx: self._reward_lock_mempool_rule(tx, new_best_height),
lambda tx: self._unknown_contract_mempool_rule(tx),
lambda tx: self._nano_activation_rule(storage, tx),
lambda tx: self._fee_tokens_activation_rule(storage, tx),
self._checkdatasig_count_rule,
)

Expand Down Expand Up @@ -428,24 +430,36 @@ def _unknown_contract_mempool_rule(self, tx: Transaction) -> bool:

def _nano_activation_rule(self, storage: TransactionStorage, tx: Transaction) -> bool:
"""Check whether a tx became invalid because the reorg changed the nano feature activation state."""
from hathor.feature_activation.utils import is_nano_active
from hathor.nanocontracts import OnChainBlueprint
from hathor.nanocontracts.utils import is_nano_active
from hathor.transaction.token_creation_tx import TokenCreationTransaction
from hathor.transaction.token_info import TokenVersion

best_block = storage.get_best_block()
if is_nano_active(settings=self._settings, block=best_block, feature_service=self.feature_service):
# When nano is active, this rule has no effect.
return True

# The nano feature activation is actually used to enable 4 use cases:

# The nano feature activation is actually used to enable 2 use cases:
if tx.is_nano_contract():
return False

if isinstance(tx, OnChainBlueprint):
return False

return True

def _fee_tokens_activation_rule(self, storage: TransactionStorage, tx: Transaction) -> bool:
"""
Check whether a tx became invalid because the reorg changed the fee-based tokens feature activation state.
"""
from hathor.transaction.token_creation_tx import TokenCreationTransaction
from hathor.transaction.token_info import TokenVersion

best_block = storage.get_best_block()
if is_fee_active(settings=self._settings, block=best_block, feature_service=self.feature_service):
# When fee-based tokens feature is active, this rule has no effect.
return True

# The fee-based tokens feature activation is actually used to enable 2 use cases:
if isinstance(tx, TokenCreationTransaction) and tx.token_version == TokenVersion.FEE:
return False

Expand Down
2 changes: 2 additions & 0 deletions hathor/feature_activation/feature.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,5 @@ class Feature(str, Enum):
COUNT_CHECKDATASIG_OP = 'COUNT_CHECKDATASIG_OP'

NANO_CONTRACTS = 'NANO_CONTRACTS'

FEE_TOKENS = 'FEE_TOKENS'
66 changes: 66 additions & 0 deletions hathor/feature_activation/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# 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 __future__ import annotations

from typing import TYPE_CHECKING, assert_never

if TYPE_CHECKING:
from hathor.conf.settings import FeatureSetting, HathorSettings
from hathor.feature_activation.feature_service import FeatureService
from hathor.transaction import Block

from hathor.feature_activation.feature import Feature


def _is_feature_active(
*,
setting: FeatureSetting,
feature: Feature,
block: Block,
feature_service: FeatureService,
) -> bool:
"""Return whether a feature is active based on the setting and block."""
# Local import to avoid circular import with hathor.conf.settings
from hathor.conf.settings import FeatureSetting

match setting:
case FeatureSetting.DISABLED:
return False
case FeatureSetting.ENABLED:
return True
case FeatureSetting.FEATURE_ACTIVATION:
return feature_service.is_feature_active(vertex=block, feature=feature)
case _: # pragma: no cover
assert_never(setting)


def is_nano_active(*, settings: HathorSettings, block: Block, feature_service: FeatureService) -> bool:
"""Return whether the Nano Contracts feature is active according to the provided settings and block."""
return _is_feature_active(
setting=settings.ENABLE_NANO_CONTRACTS,
feature=Feature.NANO_CONTRACTS,
block=block,
feature_service=feature_service,
)


def is_fee_active(*, settings: HathorSettings, block: Block, feature_service: FeatureService) -> bool:
"""Return whether the Fee feature is active according to the provided settings and block."""
return _is_feature_active(
setting=settings.ENABLE_FEE_BASED_TOKENS,
feature=Feature.FEE_TOKENS,
block=block,
feature_service=feature_service,
)
4 changes: 3 additions & 1 deletion hathor/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -401,7 +401,9 @@ def get_nc_runner(self, block: Block) -> Runner:
"""Return a contract runner for a given block."""
nc_storage_factory = self.consensus_algorithm.nc_storage_factory
block_storage = nc_storage_factory.get_block_storage_from_block(block)
return self.runner_factory.create(block_storage=block_storage)
return self.runner_factory.create(
block_storage=block_storage,
)

def get_best_block_nc_runner(self) -> Runner:
"""Return a contract runner for the best block."""
Expand Down
7 changes: 6 additions & 1 deletion hathor/nanocontracts/runner/runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -1451,7 +1451,12 @@ def __init__(
self.tx_storage = tx_storage
self.nc_storage_factory = nc_storage_factory

def create(self, *, block_storage: NCBlockStorage, seed: bytes | None = None) -> Runner:
def create(
self,
*,
block_storage: NCBlockStorage,
seed: bytes | None = None,
) -> Runner:
return Runner(
reactor=self.reactor,
settings=self.settings,
Expand Down
19 changes: 1 addition & 18 deletions hathor/nanocontracts/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,25 +16,21 @@

import hashlib
from types import ModuleType
from typing import Callable, assert_never
from typing import Callable

from cryptography.exceptions import InvalidSignature
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import ec
from pycoin.key.Key import Key as PycoinKey

from hathor.conf.settings import HathorSettings, NanoContractsSetting
from hathor.crypto.util import (
decode_address,
get_address_from_public_key_bytes,
get_public_key_bytes_compressed,
get_public_key_from_bytes_compressed,
is_pubkey_compressed,
)
from hathor.feature_activation.feature import Feature
from hathor.feature_activation.feature_service import FeatureService
from hathor.nanocontracts.types import NC_METHOD_TYPE_ATTR, BlueprintId, ContractId, NCMethodType, TokenUid, VertexId
from hathor.transaction import Block
from hathor.transaction.headers import NanoHeader
from hathor.util import not_none

Expand Down Expand Up @@ -152,19 +148,6 @@ def sign_openssl_multisig(
nano_header.nc_script = MultiSig.create_input_data(redeem_script, signatures)


def is_nano_active(*, settings: HathorSettings, block: Block, feature_service: FeatureService) -> bool:
"""Return whether the Nano Contracts feature is active according to the provided settings and block."""
match settings.ENABLE_NANO_CONTRACTS:
case NanoContractsSetting.DISABLED:
return False
case NanoContractsSetting.ENABLED:
return True
case NanoContractsSetting.FEATURE_ACTIVATION:
return feature_service.is_feature_active(vertex=block, feature=Feature.NANO_CONTRACTS)
case _: # pragma: no cover
assert_never(settings.ENABLE_NANO_CONTRACTS)


def sha3(data: bytes) -> bytes:
"""Calculate the SHA3-256 of some data."""
return hashlib.sha3_256(data).digest()
Expand Down
4 changes: 3 additions & 1 deletion hathor/transaction/vertex_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from struct import error as StructError
from typing import TYPE_CHECKING, Type

from hathor.transaction.headers import NanoHeader, VertexBaseHeader, VertexHeaderId
from hathor.transaction.headers import FeeHeader, NanoHeader, VertexBaseHeader, VertexHeaderId

if TYPE_CHECKING:
from hathor.conf.settings import HathorSettings
Expand All @@ -37,6 +37,8 @@ def get_supported_headers(settings: HathorSettings) -> dict[VertexHeaderId, Type
supported_headers: dict[VertexHeaderId, Type[VertexBaseHeader]] = {}
if settings.ENABLE_NANO_CONTRACTS:
supported_headers[VertexHeaderId.NANO_HEADER] = NanoHeader
if settings.ENABLE_FEE_BASED_TOKENS:
supported_headers[VertexHeaderId.FEE_HEADER] = FeeHeader
return supported_headers

@staticmethod
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ def verify_token_info(self, tx: TokenCreationTransaction, params: VerificationPa
# Can't create the token with NATIVE or a non-activated version
version_validations = [
tx.token_version == TokenVersion.NATIVE,
tx.token_version == TokenVersion.FEE and not params.enable_nano,
tx.token_version == TokenVersion.FEE and not params.enable_fee,
]

if any(version_validations):
Expand Down
3 changes: 3 additions & 0 deletions hathor/verification/verification_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ class VerificationParams:
reject_locked_reward: bool = True
skip_block_weight_verification: bool = False
enable_nano: bool = False
enable_fee: bool = False

reject_too_old_vertices: bool = False
harden_token_restrictions: bool = False
Expand All @@ -40,6 +41,7 @@ def default_for_mempool(
*,
best_block: Block,
enable_nano: bool = False,
enable_fee: bool = False,
) -> VerificationParams:
"""This is the appropriate parameters for verifying mempool transactions, realtime blocks and API pushes.

Expand All @@ -52,6 +54,7 @@ def default_for_mempool(
nc_block_root_id=best_block_meta.nc_block_root_id,
enable_checkdatasig_count=True,
enable_nano=enable_nano,
enable_fee=enable_fee,
reject_too_old_vertices=True,
harden_token_restrictions=True,
harden_nano_restrictions=True,
Expand Down
1 change: 1 addition & 0 deletions hathor/verification/vertex_verifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,7 @@ def get_allowed_headers(self, vertex: BaseTransaction, params: VerificationParam
case TxVersion.REGULAR_TRANSACTION | TxVersion.TOKEN_CREATION_TRANSACTION:
if params.enable_nano:
allowed_headers.add(NanoHeader)
if params.enable_fee:
allowed_headers.add(FeeHeader)
case _: # pragma: no cover
assert_never(vertex.version)
Expand Down
2 changes: 1 addition & 1 deletion hathor/version_resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@
from hathor.api_util import Resource, set_cors
from hathor.conf.get_settings import get_global_settings
from hathor.feature_activation.feature_service import FeatureService
from hathor.feature_activation.utils import is_nano_active
from hathor.manager import HathorManager
from hathor.nanocontracts.utils import is_nano_active
from hathor.util import json_dumpb


Expand Down
Loading