From 773db899b1c37a3b91484244fc187c683129552f Mon Sep 17 00:00:00 2001 From: Gabriel Levcovitz Date: Wed, 27 Aug 2025 14:30:22 -0300 Subject: [PATCH] fix(nano): fix nano_contracts_enabled on /version api --- hathor/builder/resources_builder.py | 2 +- hathor/nanocontracts/utils.py | 19 ++++++++++++++++++- hathor/verification/transaction_verifier.py | 17 ++++------------- hathor/version_resource.py | 12 ++++++++++-- tests/resources/test_version.py | 4 ++-- 5 files changed, 35 insertions(+), 19 deletions(-) diff --git a/hathor/builder/resources_builder.py b/hathor/builder/resources_builder.py index 0dd2be844..635de9892 100644 --- a/hathor/builder/resources_builder.py +++ b/hathor/builder/resources_builder.py @@ -200,7 +200,7 @@ def create_resources(self) -> server.Site: resources = [ (b'status', StatusResource(self.manager), root), - (b'version', VersionResource(self.manager), root), + (b'version', VersionResource(self.manager, self._feature_service), root), (b'create_tx', CreateTxResource(self.manager), root), (b'decode_tx', DecodeTxResource(self.manager), root), (b'validate_address', ValidateAddressResource(self.manager), root), diff --git a/hathor/nanocontracts/utils.py b/hathor/nanocontracts/utils.py index cc681dc40..d711504df 100644 --- a/hathor/nanocontracts/utils.py +++ b/hathor/nanocontracts/utils.py @@ -16,14 +16,18 @@ import hashlib from types import ModuleType -from typing import Callable +from typing import Callable, assert_never 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 +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 Vertex from hathor.transaction.headers import NanoHeader from hathor.util import not_none @@ -139,3 +143,16 @@ def sign_openssl_multisig( signatures = [privkey.sign(data, ec.ECDSA(hashes.SHA256())) for privkey in sign_privkeys] nano_header.nc_script = MultiSig.create_input_data(redeem_script, signatures) + + +def is_nano_active(settings: HathorSettings, vertex: Vertex, feature_service: FeatureService) -> bool: + """Return whether the Nano Contracts feature is active according to the provided settings and vertex.""" + 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=vertex, feature=Feature.NANO_CONTRACTS) + case _: # pragma: no cover + assert_never(settings.ENABLE_NANO_CONTRACTS) diff --git a/hathor/verification/transaction_verifier.py b/hathor/verification/transaction_verifier.py index ab969ea96..f7c435719 100644 --- a/hathor/verification/transaction_verifier.py +++ b/hathor/verification/transaction_verifier.py @@ -14,10 +14,9 @@ from __future__ import annotations -from typing import TYPE_CHECKING, assert_never +from typing import TYPE_CHECKING from hathor.daa import DifficultyAdjustmentAlgorithm -from hathor.feature_activation.feature import Feature from hathor.feature_activation.feature_service import FeatureService from hathor.profiler import get_cpu_profiler from hathor.reward_lock import get_spent_reward_locked_info @@ -267,22 +266,14 @@ def verify_sum(self, token_dict: dict[TokenUid, TokenInfo]) -> None: def verify_version(self, tx: Transaction) -> None: """Verify that the vertex version is valid.""" - from hathor.conf.settings import NanoContractsSetting + from hathor.nanocontracts.utils import is_nano_active allowed_tx_versions = { TxVersion.REGULAR_TRANSACTION, TxVersion.TOKEN_CREATION_TRANSACTION, } - match self._settings.ENABLE_NANO_CONTRACTS: - case NanoContractsSetting.DISABLED: - pass - case NanoContractsSetting.ENABLED: - allowed_tx_versions.add(TxVersion.ON_CHAIN_BLUEPRINT) - case NanoContractsSetting.FEATURE_ACTIVATION: - if self._feature_service.is_feature_active(vertex=tx, feature=Feature.NANO_CONTRACTS): - allowed_tx_versions.add(TxVersion.ON_CHAIN_BLUEPRINT) - case _ as unreachable: - assert_never(unreachable) + if is_nano_active(self._settings, tx, self._feature_service): + allowed_tx_versions.add(TxVersion.ON_CHAIN_BLUEPRINT) if tx.version not in allowed_tx_versions: raise InvalidVersionError(f'invalid vertex version: {tx.version}') diff --git a/hathor/version_resource.py b/hathor/version_resource.py index a0942e2cb..364806101 100644 --- a/hathor/version_resource.py +++ b/hathor/version_resource.py @@ -16,6 +16,9 @@ from hathor.api_util import Resource, set_cors from hathor.cli.openapi_files.register import register_resource from hathor.conf.get_settings import get_global_settings +from hathor.feature_activation.feature_service import FeatureService +from hathor.manager import HathorManager +from hathor.nanocontracts.utils import is_nano_active from hathor.util import json_dumpb @@ -27,10 +30,12 @@ class VersionResource(Resource): """ isLeaf = True - def __init__(self, manager): + def __init__(self, manager: HathorManager, feature_service: FeatureService) -> None: # Important to have the manager so we can have access to min_tx_weight_coefficient + super().__init__() self._settings = get_global_settings() self.manager = manager + self.feature_service = feature_service def render_GET(self, request): """ GET request for /version/ that returns the API version @@ -40,10 +45,13 @@ def render_GET(self, request): request.setHeader(b'content-type', b'application/json; charset=utf-8') set_cors(request, 'GET') + best_block = self.manager.tx_storage.get_best_block() + nano_contracts_enabled = is_nano_active(self._settings, best_block, self.feature_service) + data = { 'version': hathor.__version__, 'network': self.manager.network, - 'nano_contracts_enabled': self._settings.ENABLE_NANO_CONTRACTS, + 'nano_contracts_enabled': nano_contracts_enabled, 'min_weight': self._settings.MIN_TX_WEIGHT, # DEPRECATED 'min_tx_weight': self._settings.MIN_TX_WEIGHT, 'min_tx_weight_coefficient': self._settings.MIN_TX_WEIGHT_COEFFICIENT, diff --git a/tests/resources/test_version.py b/tests/resources/test_version.py index 9c0bac278..fd6be2eb9 100644 --- a/tests/resources/test_version.py +++ b/tests/resources/test_version.py @@ -1,7 +1,7 @@ import shutil import subprocess import tempfile -from unittest.mock import patch +from unittest.mock import Mock, patch from twisted.internet.defer import inlineCallbacks @@ -14,7 +14,7 @@ class VersionTest(_BaseResourceTest._ResourceTest): def setUp(self): super().setUp() - self.web = StubSite(VersionResource(self.manager)) + self.web = StubSite(VersionResource(self.manager, Mock())) self.tmp_dir = tempfile.mkdtemp() def tearDown(self):