Skip to content

Commit 1d39805

Browse files
committed
refactor(metadata): pre-refactors for migrating feature states to static metadata
1 parent ddb6d5e commit 1d39805

19 files changed

+236
-326
lines changed

hathor/builder/builder.py

+2-5
Original file line numberDiff line numberDiff line change
@@ -535,10 +535,7 @@ def _get_or_create_feature_service(self) -> FeatureService:
535535
if self._feature_service is None:
536536
settings = self._get_or_create_settings()
537537
tx_storage = self._get_or_create_tx_storage()
538-
self._feature_service = FeatureService(
539-
feature_settings=settings.FEATURE_ACTIVATION,
540-
tx_storage=tx_storage
541-
)
538+
self._feature_service = FeatureService(settings=settings, tx_storage=tx_storage)
542539

543540
return self._feature_service
544541

@@ -549,7 +546,7 @@ def _get_or_create_bit_signaling_service(self) -> BitSignalingService:
549546
feature_service = self._get_or_create_feature_service()
550547
feature_storage = self._get_or_create_feature_storage()
551548
self._bit_signaling_service = BitSignalingService(
552-
feature_settings=settings.FEATURE_ACTIVATION,
549+
settings=settings,
553550
feature_service=feature_service,
554551
tx_storage=tx_storage,
555552
support_features=self._support_features,

hathor/builder/cli_builder.py

+3-6
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ def create_manager(self, reactor: Reactor) -> HathorManager:
149149
else:
150150
indexes = RocksDBIndexesManager(self.rocksdb_storage)
151151

152-
kwargs = {}
152+
kwargs: dict[str, Any] = {}
153153
if not self._args.cache:
154154
# We should only pass indexes if cache is disabled. Otherwise,
155155
# only TransactionCacheStorage should have indexes.
@@ -268,13 +268,10 @@ def create_manager(self, reactor: Reactor) -> HathorManager:
268268
self.log.info('--x-enable-event-queue flag provided. '
269269
'The events detected by the full node will be stored and can be retrieved by clients')
270270

271-
self.feature_service = FeatureService(
272-
feature_settings=settings.FEATURE_ACTIVATION,
273-
tx_storage=tx_storage
274-
)
271+
self.feature_service = FeatureService(settings=settings, tx_storage=tx_storage)
275272

276273
bit_signaling_service = BitSignalingService(
277-
feature_settings=settings.FEATURE_ACTIVATION,
274+
settings=settings,
278275
feature_service=self.feature_service,
279276
tx_storage=tx_storage,
280277
support_features=self._args.signal_support,

hathor/builder/resources_builder.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,7 @@ def create_resources(self) -> server.Site:
237237
(
238238
b'feature',
239239
FeatureResource(
240-
feature_settings=settings.FEATURE_ACTIVATION,
240+
settings=settings,
241241
feature_service=self._feature_service,
242242
tx_storage=self.manager.tx_storage
243243
),

hathor/feature_activation/bit_signaling_service.py

+9-9
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@
1414

1515
from structlog import get_logger
1616

17+
from hathor.conf.settings import HathorSettings
1718
from hathor.feature_activation.feature import Feature
1819
from hathor.feature_activation.feature_service import FeatureService
1920
from hathor.feature_activation.model.criteria import Criteria
2021
from hathor.feature_activation.model.feature_state import FeatureState
21-
from hathor.feature_activation.settings import Settings as FeatureSettings
2222
from hathor.feature_activation.storage.feature_activation_storage import FeatureActivationStorage
2323
from hathor.transaction import Block
2424
from hathor.transaction.storage import TransactionStorage
@@ -29,7 +29,7 @@
2929
class BitSignalingService:
3030
__slots__ = (
3131
'_log',
32-
'_feature_settings',
32+
'_settings',
3333
'_feature_service',
3434
'_tx_storage',
3535
'_support_features',
@@ -40,15 +40,15 @@ class BitSignalingService:
4040
def __init__(
4141
self,
4242
*,
43-
feature_settings: FeatureSettings,
43+
settings: HathorSettings,
4444
feature_service: FeatureService,
4545
tx_storage: TransactionStorage,
4646
support_features: set[Feature],
4747
not_support_features: set[Feature],
4848
feature_storage: FeatureActivationStorage | None,
4949
) -> None:
5050
self._log = logger.new()
51-
self._feature_settings = feature_settings
51+
self._settings = settings
5252
self._feature_service = feature_service
5353
self._tx_storage = tx_storage
5454
self._support_features = support_features
@@ -163,14 +163,14 @@ def _log_signal_bits(self, feature: Feature, enable_bit: bool, support: bool, no
163163

164164
def _get_signaling_features(self, block: Block) -> dict[Feature, Criteria]:
165165
"""Given a specific block, return all features that are in a signaling state for that block."""
166-
feature_descriptions = self._feature_service.get_bits_description(block=block)
166+
feature_infos = self._feature_service.get_feature_infos(block=block)
167167
signaling_features = {
168-
feature: description.criteria
169-
for feature, description in feature_descriptions.items()
170-
if description.state in FeatureState.get_signaling_states()
168+
feature: feature_info.criteria
169+
for feature, feature_info in feature_infos.items()
170+
if feature_info.state in FeatureState.get_signaling_states()
171171
}
172172

173-
assert len(signaling_features) <= self._feature_settings.max_signal_bits, (
173+
assert len(signaling_features) <= self._settings.FEATURE_ACTIVATION.max_signal_bits, (
174174
'Invalid state. Signaling more features than the allowed maximum.'
175175
)
176176

hathor/feature_activation/feature_service.py

+15-13
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,10 @@
1515
from dataclasses import dataclass
1616
from typing import TYPE_CHECKING, Optional, TypeAlias
1717

18+
from hathor.conf.settings import HathorSettings
1819
from hathor.feature_activation.feature import Feature
19-
from hathor.feature_activation.model.feature_description import FeatureDescription
20+
from hathor.feature_activation.model.feature_info import FeatureInfo
2021
from hathor.feature_activation.model.feature_state import FeatureState
21-
from hathor.feature_activation.settings import Settings as FeatureSettings
2222

2323
if TYPE_CHECKING:
2424
from hathor.feature_activation.bit_signaling_service import BitSignalingService
@@ -44,8 +44,8 @@ class BlockIsMissingSignal:
4444
class FeatureService:
4545
__slots__ = ('_feature_settings', '_tx_storage', 'bit_signaling_service')
4646

47-
def __init__(self, *, feature_settings: FeatureSettings, tx_storage: 'TransactionStorage') -> None:
48-
self._feature_settings = feature_settings
47+
def __init__(self, *, settings: HathorSettings, tx_storage: 'TransactionStorage') -> None:
48+
self._feature_settings = settings.FEATURE_ACTIVATION
4949
self._tx_storage = tx_storage
5050
self.bit_signaling_service: Optional['BitSignalingService'] = None
5151

@@ -64,11 +64,11 @@ def is_signaling_mandatory_features(self, block: 'Block') -> BlockSignalingState
6464
height = block.static_metadata.height
6565
offset_to_boundary = height % self._feature_settings.evaluation_interval
6666
remaining_blocks = self._feature_settings.evaluation_interval - offset_to_boundary - 1
67-
descriptions = self.get_bits_description(block=block)
67+
feature_infos = self.get_feature_infos(block=block)
6868

6969
must_signal_features = (
70-
feature for feature, description in descriptions.items()
71-
if description.state is FeatureState.MUST_SIGNAL
70+
feature for feature, feature_info in feature_infos.items()
71+
if feature_info.state is FeatureState.MUST_SIGNAL
7272
)
7373

7474
for feature in must_signal_features:
@@ -192,12 +192,12 @@ def _calculate_new_state(
192192
if previous_state is FeatureState.FAILED:
193193
return FeatureState.FAILED
194194

195-
raise ValueError(f'Unknown previous state: {previous_state}')
195+
raise NotImplementedError(f'Unknown previous state: {previous_state}')
196196

197-
def get_bits_description(self, *, block: 'Block') -> dict[Feature, FeatureDescription]:
197+
def get_feature_infos(self, *, block: 'Block') -> dict[Feature, FeatureInfo]:
198198
"""Returns the criteria definition and feature state for all features at a certain block."""
199199
return {
200-
feature: FeatureDescription(
200+
feature: FeatureInfo(
201201
criteria=criteria,
202202
state=self.get_state(block=block, feature=feature)
203203
)
@@ -223,9 +223,11 @@ def _get_ancestor_at_height(self, *, block: 'Block', ancestor_height: int) -> 'B
223223
if parent_block.static_metadata.height == ancestor_height:
224224
return parent_block
225225

226-
if not parent_metadata.voided_by and (ancestor := self._tx_storage.get_block_by_height(ancestor_height)):
227-
from hathor.transaction import Block
228-
assert isinstance(ancestor, Block)
226+
if not parent_metadata.voided_by:
227+
ancestor = self._tx_storage.get_block_by_height(ancestor_height)
228+
assert ancestor is not None, (
229+
'it is guaranteed that the ancestor of a fully connected and non-voided block is in the height index'
230+
)
229231
return ancestor
230232

231233
return self._get_ancestor_iteratively(block=parent_block, ancestor_height=ancestor_height)

hathor/feature_activation/model/feature_description.py renamed to hathor/feature_activation/model/feature_info.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
from hathor.feature_activation.model.feature_state import FeatureState
1919

2020

21-
class FeatureDescription(NamedTuple):
21+
class FeatureInfo(NamedTuple):
2222
"""Represents all information related to one feature, that is, its criteria and state."""
2323
criteria: Criteria
2424
state: FeatureState

hathor/feature_activation/model/feature_state.py

+7-2
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,11 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15-
from enum import Enum
15+
from enum import Enum, unique
1616

1717

18-
class FeatureState(Enum):
18+
@unique
19+
class FeatureState(str, Enum):
1920
"""
2021
Possible states a feature can be in, for each block.
2122
@@ -35,6 +36,10 @@ class FeatureState(Enum):
3536
ACTIVE = 'ACTIVE'
3637
FAILED = 'FAILED'
3738

39+
def is_active(self) -> bool:
40+
"""Return whether the state is active."""
41+
return self is FeatureState.ACTIVE
42+
3843
@staticmethod
3944
def get_signaling_states() -> set['FeatureState']:
4045
"""

hathor/feature_activation/resources/feature.py

+13-11
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,10 @@
1818

1919
from hathor.api_util import Resource, set_cors
2020
from hathor.cli.openapi_files.register import register_resource
21+
from hathor.conf.settings import HathorSettings
2122
from hathor.feature_activation.feature import Feature
2223
from hathor.feature_activation.feature_service import FeatureService
2324
from hathor.feature_activation.model.feature_state import FeatureState
24-
from hathor.feature_activation.settings import Settings as FeatureSettings
2525
from hathor.transaction import Block
2626
from hathor.transaction.storage import TransactionStorage
2727
from hathor.utils.api import ErrorResponse, QueryParams, Response
@@ -36,12 +36,12 @@ class FeatureResource(Resource):
3636
def __init__(
3737
self,
3838
*,
39-
feature_settings: FeatureSettings,
39+
settings: HathorSettings,
4040
feature_service: FeatureService,
4141
tx_storage: TransactionStorage
4242
) -> None:
4343
super().__init__()
44-
self._feature_settings = feature_settings
44+
self._feature_settings = settings.FEATURE_ACTIVATION
4545
self._feature_service = feature_service
4646
self.tx_storage = tx_storage
4747

@@ -68,17 +68,17 @@ def get_block_features(self, request: Request) -> bytes:
6868
return error.json_dumpb()
6969

7070
signal_bits = []
71-
feature_descriptions = self._feature_service.get_bits_description(block=block)
71+
feature_infos = self._feature_service.get_feature_infos(block=block)
7272

73-
for feature, description in feature_descriptions.items():
74-
if description.state not in FeatureState.get_signaling_states():
73+
for feature, feature_info in feature_infos.items():
74+
if feature_info.state not in FeatureState.get_signaling_states():
7575
continue
7676

7777
block_feature = GetBlockFeatureResponse(
78-
bit=description.criteria.bit,
79-
signal=block.get_feature_activation_bit_value(description.criteria.bit),
78+
bit=feature_info.criteria.bit,
79+
signal=block.get_feature_activation_bit_value(feature_info.criteria.bit),
8080
feature=feature,
81-
feature_state=description.state.name
81+
feature_state=feature_info.state.name
8282
)
8383

8484
signal_bits.append(block_feature)
@@ -90,10 +90,12 @@ def get_block_features(self, request: Request) -> bytes:
9090
def get_features(self) -> bytes:
9191
best_block = self.tx_storage.get_best_block()
9292
bit_counts = best_block.static_metadata.feature_activation_bit_counts
93+
feature_infos = self._feature_service.get_feature_infos(block=best_block)
9394
features = []
9495

95-
for feature, criteria in self._feature_settings.features.items():
96-
state = self._feature_service.get_state(block=best_block, feature=feature)
96+
for feature, feature_info in feature_infos.items():
97+
state = feature_info.state
98+
criteria = feature_info.criteria
9799
threshold_count = criteria.get_threshold(self._feature_settings)
98100
threshold_percentage = threshold_count / self._feature_settings.evaluation_interval
99101
acceptance_percentage = None

hathor/transaction/__init__.py

+2
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
TxInput,
2020
TxOutput,
2121
TxVersion,
22+
Vertex,
2223
sum_weights,
2324
)
2425
from hathor.transaction.block import Block
@@ -29,6 +30,7 @@
2930
__all__ = [
3031
'Transaction',
3132
'BitcoinAuxPow',
33+
'Vertex',
3234
'BaseTransaction',
3335
'Block',
3436
'MergeMinedBlock',

hathor/transaction/static_metadata.py

+3-11
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,7 @@
1414

1515
from __future__ import annotations
1616

17-
import dataclasses
1817
from abc import ABC
19-
from dataclasses import dataclass
2018
from itertools import chain, starmap, zip_longest
2119
from operator import add
2220
from typing import TYPE_CHECKING, Callable
@@ -26,16 +24,16 @@
2624
from hathor.feature_activation.feature import Feature
2725
from hathor.feature_activation.model.feature_state import FeatureState
2826
from hathor.types import VertexId
29-
from hathor.util import json_dumpb, json_loadb
27+
from hathor.util import json_loadb
28+
from hathor.utils.pydantic import BaseModel
3029

3130
if TYPE_CHECKING:
3231
from hathor.conf.settings import HathorSettings
3332
from hathor.transaction import BaseTransaction, Block, Transaction
3433
from hathor.transaction.storage import TransactionStorage
3534

3635

37-
@dataclass(slots=True, frozen=True, kw_only=True)
38-
class VertexStaticMetadata(ABC):
36+
class VertexStaticMetadata(ABC, BaseModel):
3937
"""
4038
Static Metadata represents vertex attributes that are not intrinsic to the vertex data, but can be calculated from
4139
only the vertex itself and its dependencies, and whose values never change.
@@ -49,10 +47,6 @@ class VertexStaticMetadata(ABC):
4947
# metadata (that does not have this calculated, from a tx with a new format that does have this calculated)
5048
min_height: int
5149

52-
def to_bytes(self) -> bytes:
53-
"""Convert this static metadata instance to a json bytes representation."""
54-
return json_dumpb(dataclasses.asdict(self))
55-
5650
@classmethod
5751
def from_bytes(cls, data: bytes, *, target: 'BaseTransaction') -> 'VertexStaticMetadata':
5852
"""Create a static metadata instance from a json bytes representation, with a known vertex type target."""
@@ -68,7 +62,6 @@ def from_bytes(cls, data: bytes, *, target: 'BaseTransaction') -> 'VertexStaticM
6862
raise NotImplementedError
6963

7064

71-
@dataclass(slots=True, frozen=True, kw_only=True)
7265
class BlockStaticMetadata(VertexStaticMetadata):
7366
height: int
7467

@@ -181,7 +174,6 @@ def _get_previous_feature_activation_bit_counts(
181174
return parent_block.static_metadata.feature_activation_bit_counts
182175

183176

184-
@dataclass(slots=True, frozen=True, kw_only=True)
185177
class TransactionStaticMetadata(VertexStaticMetadata):
186178
@classmethod
187179
def create_from_storage(cls, tx: 'Transaction', settings: HathorSettings, storage: 'TransactionStorage') -> Self:

hathor/transaction/storage/rocksdb_storage.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ def _save_transaction(self, tx: 'BaseTransaction', *, only_metadata: bool = Fals
113113

114114
@override
115115
def _save_static_metadata(self, tx: 'BaseTransaction') -> None:
116-
self._db.put((self._cf_static_meta, tx.hash), tx.static_metadata.to_bytes())
116+
self._db.put((self._cf_static_meta, tx.hash), tx.static_metadata.json_dumpb())
117117

118118
def _load_static_metadata(self, vertex: 'BaseTransaction') -> None:
119119
"""Set vertex static metadata loaded from what's saved in this storage."""

hathor/verification/block_verifier.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
from typing_extensions import assert_never
16+
1517
from hathor.conf.settings import HathorSettings
1618
from hathor.daa import DifficultyAdjustmentAlgorithm
1719
from hathor.feature_activation.feature_service import BlockIsMissingSignal, BlockIsSignaling, FeatureService
@@ -93,5 +95,4 @@ def verify_mandatory_signaling(self, block: Block) -> None:
9395
f"Block must signal support for feature '{feature.value}' during MUST_SIGNAL phase."
9496
)
9597
case _:
96-
# TODO: This will be changed to assert_never() so mypy can check it.
97-
raise NotImplementedError
98+
assert_never(signaling_state)

hathor/verification/verification_service.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,7 @@ def _verify_block(self, block: Block) -> None:
194194
self.verifiers.block.verify_mandatory_signaling(block)
195195

196196
def _verify_merge_mined_block(self, block: MergeMinedBlock) -> None:
197+
self.verifiers.merge_mined_block.verify_aux_pow(block)
197198
self._verify_block(block)
198199

199200
def _verify_poa_block(self, block: PoaBlock) -> None:
@@ -274,7 +275,6 @@ def _verify_without_storage_block(self, block: Block) -> None:
274275
self._verify_without_storage_base_block(block)
275276

276277
def _verify_without_storage_merge_mined_block(self, block: MergeMinedBlock) -> None:
277-
self.verifiers.merge_mined_block.verify_aux_pow(block)
278278
self._verify_without_storage_block(block)
279279

280280
def _verify_without_storage_poa_block(self, block: PoaBlock) -> None:

0 commit comments

Comments
 (0)