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/consensus/block_consensus.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

from hathor.consensus.context import ReorgInfo
from hathor.execution_manager import non_critical_code
from hathor.feature_activation.utils import Features
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 @@ -147,9 +148,9 @@ def _should_execute_nano(self, block: Block) -> bool:
"""
assert not block.is_genesis

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)
features = Features.from_vertex(settings=self._settings, feature_service=self.feature_service, vertex=parent)
return features.nanocontracts

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
13 changes: 9 additions & 4 deletions hathor/consensus/consensus.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
from hathor.consensus.context import ConsensusAlgorithmContext
from hathor.consensus.transaction_consensus import TransactionConsensusAlgorithmFactory
from hathor.execution_manager import non_critical_code
from hathor.feature_activation.utils import is_fee_active
from hathor.feature_activation.utils import Features
from hathor.profiler import get_cpu_profiler
from hathor.pubsub import HathorEvents, PubSubManager
from hathor.transaction import BaseTransaction, Transaction
Expand Down Expand Up @@ -418,11 +418,13 @@ 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

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

Expand All @@ -443,7 +445,10 @@ def _fee_tokens_activation_rule(self, storage: TransactionStorage, tx: Transacti
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):
features = Features.from_vertex(
settings=self._settings, vertex=best_block, feature_service=self.feature_service
)
if features.fee_tokens:
# When fee-based tokens feature is active, this rule has no effect.
return True

Expand Down
6 changes: 2 additions & 4 deletions hathor/feature_activation/feature.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@
# See the License for the specific language governing permissions and
# limitations under the License.

from enum import Enum, unique
from enum import StrEnum, unique


@unique
class Feature(str, Enum):
class Feature(StrEnum):
"""
An enum containing all features that participate in the feature activation process, past or future, activated
or not, for all networks. Features should NOT be removed from this enum, to preserve history. Their values
Expand All @@ -30,7 +30,5 @@ class Feature(str, Enum):

INCREASE_MAX_MERKLE_PATH_LENGTH = 'INCREASE_MAX_MERKLE_PATH_LENGTH'
COUNT_CHECKDATASIG_OP = 'COUNT_CHECKDATASIG_OP'

NANO_CONTRACTS = 'NANO_CONTRACTS'

FEE_TOKENS = 'FEE_TOKENS'
71 changes: 38 additions & 33 deletions hathor/feature_activation/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,53 +14,58 @@

from __future__ import annotations

from dataclasses import dataclass
from typing import TYPE_CHECKING, assert_never

from hathor.feature_activation.feature import Feature
from hathor.feature_activation.model.feature_state import FeatureState

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.transaction import Vertex

from hathor.feature_activation.feature import Feature

@dataclass(slots=True, frozen=True, kw_only=True)
class Features:
"""A dataclass holding state information about features from the Feature Activation process."""

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
count_checkdatasig_op: bool
nanocontracts: bool
fee_tokens: bool

@staticmethod
def from_vertex(*, settings: HathorSettings, feature_service: FeatureService, vertex: Vertex) -> Features:
"""Return whether the Nano Contracts feature is active according to the provided settings and vertex."""
from hathor.conf.settings import FeatureSetting
feature_states = feature_service.get_feature_states(vertex=vertex)
feature_settings = {
Feature.COUNT_CHECKDATASIG_OP: FeatureSetting.FEATURE_ACTIVATION,
Feature.NANO_CONTRACTS: settings.ENABLE_NANO_CONTRACTS,
Feature.FEE_TOKENS: settings.ENABLE_FEE_BASED_TOKENS,
}

feature_is_active: dict[Feature, bool] = {
feature: _is_feature_active(setting, feature_states.get(feature, FeatureState.DEFINED))
for feature, setting in feature_settings.items()
}

return Features(
count_checkdatasig_op=feature_is_active[Feature.COUNT_CHECKDATASIG_OP],
nanocontracts=feature_is_active[Feature.NANO_CONTRACTS],
fee_tokens=feature_is_active[Feature.FEE_TOKENS],
)


def _is_feature_active(setting: FeatureSetting, state: FeatureState) -> bool:
"""Return whether a feature is active based on the setting and state."""
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)
return state.is_active()
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,
)
7 changes: 6 additions & 1 deletion hathor/p2p/sync_v2/transaction_streaming_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from structlog import get_logger
from twisted.internet.defer import Deferred, inlineCallbacks

from hathor.feature_activation.utils import Features
from hathor.p2p.sync_v2.exception import (
InvalidVertexError,
StreamingError,
Expand Down Expand Up @@ -53,8 +54,12 @@ def __init__(self,
# 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(
enable_checkdatasig_count=False,
nc_block_root_id=None,
features=Features(
count_checkdatasig_op=False,
nanocontracts=False,
fee_tokens=False,
)
)

self.reactor = sync_agent.reactor
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_fee,
tx.token_version == TokenVersion.FEE and not params.features.fee_tokens,
]

if any(version_validations):
Expand Down
2 changes: 1 addition & 1 deletion hathor/verification/transaction_verifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,7 @@ def verify_version(self, tx: Transaction, params: VerificationParams) -> None:
TxVersion.TOKEN_CREATION_TRANSACTION,
}

if params.enable_nano:
if params.features.nanocontracts:
allowed_tx_versions.add(TxVersion.ON_CHAIN_BLUEPRINT)

if tx.version not in allowed_tx_versions:
Expand Down
25 changes: 12 additions & 13 deletions hathor/verification/verification_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

from dataclasses import dataclass

from hathor.feature_activation.utils import Features
from hathor.transaction import Block


Expand All @@ -24,37 +25,35 @@ class VerificationParams:
"""Contains every parameter/setting to run a single verification."""

nc_block_root_id: bytes | None
enable_checkdatasig_count: bool
reject_locked_reward: bool = True
skip_block_weight_verification: bool = False
enable_nano: bool = False
enable_fee: bool = False
features: Features

reject_too_old_vertices: bool = False
harden_token_restrictions: bool = False
harden_nano_restrictions: bool = False
reject_conflicts_with_confirmed_txs: bool = False

@classmethod
def default_for_mempool(
cls,
*,
best_block: Block,
enable_nano: bool = False,
enable_fee: bool = False,
) -> VerificationParams:
def default_for_mempool(cls, *, best_block: Block, features: Features | None = None) -> VerificationParams:
"""This is the appropriate parameters for verifying mempool transactions, realtime blocks and API pushes.

Other cases should instantiate `VerificationParams` manually with the appropriate parameter values.
"""
best_block_meta = best_block.get_metadata()
if best_block_meta.nc_block_root_id is None:
assert best_block.is_genesis

if features is None:
features = Features(
count_checkdatasig_op=True,
nanocontracts=True,
fee_tokens=False
)

return cls(
nc_block_root_id=best_block_meta.nc_block_root_id,
enable_checkdatasig_count=True,
enable_nano=enable_nano,
enable_fee=enable_fee,
features=features,
reject_too_old_vertices=True,
harden_token_restrictions=True,
harden_nano_restrictions=True,
Expand Down
6 changes: 3 additions & 3 deletions hathor/verification/verification_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ def _verify_tx(
# TODO do genesis validation
return
self.verify_without_storage(tx, params)
self.verifiers.tx.verify_sigops_input(tx, params.enable_checkdatasig_count)
self.verifiers.tx.verify_sigops_input(tx, params.features.count_checkdatasig_op)
self.verifiers.tx.verify_inputs(tx) # need to run verify_inputs first to check if all inputs exist
self.verifiers.tx.verify_version(tx, params)

Expand Down Expand Up @@ -327,7 +327,7 @@ def _verify_without_storage_base_block(self, block: Block, params: VerificationP
self.verifiers.vertex.verify_outputs(block)
self.verifiers.block.verify_output_token_indexes(block)
self.verifiers.block.verify_data(block)
self.verifiers.vertex.verify_sigops_output(block, params.enable_checkdatasig_count)
self.verifiers.vertex.verify_sigops_output(block, params.features.count_checkdatasig_op)

def _verify_without_storage_block(self, block: Block, params: VerificationParams) -> None:
""" Run all verifications that do not need a storage.
Expand All @@ -349,7 +349,7 @@ def _verify_without_storage_tx(self, tx: Transaction, params: VerificationParams
self.verifiers.tx.verify_number_of_inputs(tx)
self.verifiers.vertex.verify_outputs(tx)
self.verifiers.tx.verify_output_token_indexes(tx)
self.verifiers.vertex.verify_sigops_output(tx, params.enable_checkdatasig_count)
self.verifiers.vertex.verify_sigops_output(tx, params.features.count_checkdatasig_op)
self.verifiers.tx.verify_tokens(tx, params)

def _verify_without_storage_token_creation_tx(
Expand Down
4 changes: 2 additions & 2 deletions hathor/verification/vertex_verifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -226,9 +226,9 @@ def get_allowed_headers(self, vertex: BaseTransaction, params: VerificationParam
case TxVersion.ON_CHAIN_BLUEPRINT:
pass
case TxVersion.REGULAR_TRANSACTION | TxVersion.TOKEN_CREATION_TRANSACTION:
if params.enable_nano:
if params.features.nanocontracts:
allowed_headers.add(NanoHeader)
if params.enable_fee:
if params.features.fee_tokens:
allowed_headers.add(FeeHeader)
case _: # pragma: no cover
assert_never(vertex.version)
Expand Down
7 changes: 4 additions & 3 deletions hathor/version_resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
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.feature_activation.utils import Features
from hathor.manager import HathorManager
from hathor.util import json_dumpb

Expand Down Expand Up @@ -46,9 +46,10 @@ def render_GET(self, request):
set_cors(request, 'GET')

best_block = self.manager.tx_storage.get_best_block()
nano_contracts_enabled = is_nano_active(
settings=self._settings, block=best_block, feature_service=self.feature_service
features = Features.from_vertex(
settings=self._settings, vertex=best_block, feature_service=self.feature_service
)
nano_contracts_enabled = features.nanocontracts

data = {
'version': hathor.__version__,
Expand Down
Loading