Skip to content

Commit 4fcfecc

Browse files
committed
refactor(metadata): pre-refactors for migrating feature states to static metadata
1 parent 9e960f7 commit 4fcfecc

23 files changed

+564
-720
lines changed

hathor/builder/builder.py

+12-13
Original file line numberDiff line numberDiff line change
@@ -266,11 +266,6 @@ def set_event_manager(self, event_manager: EventManager) -> 'Builder':
266266
self._event_manager = event_manager
267267
return self
268268

269-
def set_feature_service(self, feature_service: FeatureService) -> 'Builder':
270-
self.check_if_can_modify()
271-
self._feature_service = feature_service
272-
return self
273-
274269
def set_bit_signaling_service(self, bit_signaling_service: BitSignalingService) -> 'Builder':
275270
self.check_if_can_modify()
276271
self._bit_signaling_service = bit_signaling_service
@@ -413,6 +408,7 @@ def _get_or_create_indexes_manager(self) -> IndexesManager:
413408

414409
def _get_or_create_tx_storage(self) -> TransactionStorage:
415410
indexes = self._get_or_create_indexes_manager()
411+
settings = self._get_or_create_settings()
416412

417413
if self._tx_storage is not None:
418414
# If a tx storage is provided, set the indexes manager to it.
@@ -424,11 +420,11 @@ def _get_or_create_tx_storage(self) -> TransactionStorage:
424420
store_indexes = None
425421

426422
if self._storage_type == StorageType.MEMORY:
427-
self._tx_storage = TransactionMemoryStorage(indexes=store_indexes)
423+
self._tx_storage = TransactionMemoryStorage(indexes=store_indexes, settings=settings)
428424

429425
elif self._storage_type == StorageType.ROCKSDB:
430426
rocksdb_storage = self._get_or_create_rocksdb_storage()
431-
self._tx_storage = TransactionRocksDBStorage(rocksdb_storage, indexes=store_indexes)
427+
self._tx_storage = TransactionRocksDBStorage(rocksdb_storage, indexes=store_indexes, settings=settings)
432428

433429
else:
434430
raise NotImplementedError
@@ -438,7 +434,13 @@ def _get_or_create_tx_storage(self) -> TransactionStorage:
438434
kwargs: dict[str, Any] = {}
439435
if self._tx_storage_cache_capacity is not None:
440436
kwargs['capacity'] = self._tx_storage_cache_capacity
441-
self._tx_storage = TransactionCacheStorage(self._tx_storage, reactor, indexes=indexes, **kwargs)
437+
self._tx_storage = TransactionCacheStorage(
438+
self._tx_storage,
439+
reactor,
440+
indexes=indexes,
441+
settings=settings,
442+
**kwargs
443+
)
442444

443445
return self._tx_storage
444446

@@ -482,10 +484,7 @@ def _get_or_create_feature_service(self) -> FeatureService:
482484
if self._feature_service is None:
483485
settings = self._get_or_create_settings()
484486
tx_storage = self._get_or_create_tx_storage()
485-
self._feature_service = FeatureService(
486-
feature_settings=settings.FEATURE_ACTIVATION,
487-
tx_storage=tx_storage
488-
)
487+
self._feature_service = FeatureService(settings=settings, tx_storage=tx_storage)
489488

490489
return self._feature_service
491490

@@ -496,7 +495,7 @@ def _get_or_create_bit_signaling_service(self) -> BitSignalingService:
496495
feature_service = self._get_or_create_feature_service()
497496
feature_storage = self._get_or_create_feature_storage()
498497
self._bit_signaling_service = BitSignalingService(
499-
feature_settings=settings.FEATURE_ACTIVATION,
498+
settings=settings,
500499
feature_service=feature_service,
501500
tx_storage=tx_storage,
502501
support_features=self._support_features,

hathor/builder/cli_builder.py

+3-6
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ def create_manager(self, reactor: Reactor) -> HathorManager:
146146
else:
147147
indexes = RocksDBIndexesManager(self.rocksdb_storage)
148148

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

255-
self.feature_service = FeatureService(
256-
feature_settings=settings.FEATURE_ACTIVATION,
257-
tx_storage=tx_storage
258-
)
255+
self.feature_service = FeatureService(settings=settings, tx_storage=tx_storage)
259256

260257
bit_signaling_service = BitSignalingService(
261-
feature_settings=settings.FEATURE_ACTIVATION,
258+
settings=settings,
262259
feature_service=self.feature_service,
263260
tx_storage=tx_storage,
264261
support_features=self._args.signal_support,

hathor/builder/resources_builder.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ def create_resources(self) -> server.Site:
206206
(
207207
b'feature',
208208
FeatureResource(
209-
feature_settings=settings.FEATURE_ACTIVATION,
209+
settings=settings,
210210
feature_service=self._feature_service,
211211
tx_storage=self.manager.tx_storage
212212
),

hathor/feature_activation/bit_signaling_service.py

+9-10
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,23 +40,22 @@ 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
5555
self._not_support_features = not_support_features
5656
self._feature_storage = feature_storage
5757

5858
self._validate_support_intersection()
59-
self._feature_service.bit_signaling_service = self
6059

6160
def start(self) -> None:
6261
"""
@@ -163,14 +162,14 @@ def _log_signal_bits(self, feature: Feature, enable_bit: bool, support: bool, no
163162

164163
def _get_signaling_features(self, block: Block) -> dict[Feature, Criteria]:
165164
"""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)
165+
feature_infos = self._feature_service.get_feature_infos(block=block)
167166
signaling_features = {
168-
feature: description.criteria
169-
for feature, description in feature_descriptions.items()
170-
if description.state in FeatureState.get_signaling_states()
167+
feature: feature_info.criteria
168+
for feature, feature_info in feature_infos.items()
169+
if feature_info.state in FeatureState.get_signaling_states()
171170
}
172171

173-
assert len(signaling_features) <= self._feature_settings.max_signal_bits, (
172+
assert len(signaling_features) <= self._settings.FEATURE_ACTIVATION.max_signal_bits, (
174173
'Invalid state. Signaling more features than the allowed maximum.'
175174
)
176175

hathor/feature_activation/feature_service.py

+17-21
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,14 @@
1313
# limitations under the License.
1414

1515
from dataclasses import dataclass
16-
from typing import TYPE_CHECKING, Optional, TypeAlias
16+
from typing import TYPE_CHECKING, 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:
24-
from hathor.feature_activation.bit_signaling_service import BitSignalingService
2524
from hathor.transaction import Block
2625
from hathor.transaction.storage import TransactionStorage
2726

@@ -42,12 +41,11 @@ class BlockIsMissingSignal:
4241

4342

4443
class FeatureService:
45-
__slots__ = ('_feature_settings', '_tx_storage', 'bit_signaling_service')
44+
__slots__ = ('_feature_settings', '_tx_storage')
4645

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

5250
def is_feature_active(self, *, block: 'Block', feature: Feature) -> bool:
5351
"""Returns whether a Feature is active at a certain block."""
@@ -64,11 +62,11 @@ def is_signaling_mandatory_features(self, block: 'Block') -> BlockSignalingState
6462
height = block.static_metadata.height
6563
offset_to_boundary = height % self._feature_settings.evaluation_interval
6664
remaining_blocks = self._feature_settings.evaluation_interval - offset_to_boundary - 1
67-
descriptions = self.get_bits_description(block=block)
65+
feature_infos = self.get_feature_infos(block=block)
6866

6967
must_signal_features = (
70-
feature for feature, description in descriptions.items()
71-
if description.state is FeatureState.MUST_SIGNAL
68+
feature for feature, feature_info in feature_infos.items()
69+
if feature_info.state is FeatureState.MUST_SIGNAL
7270
)
7371

7472
for feature in must_signal_features:
@@ -115,10 +113,6 @@ def get_state(self, *, block: 'Block', feature: Feature) -> FeatureState:
115113
previous_state=previous_boundary_state
116114
)
117115

118-
if new_state == FeatureState.MUST_SIGNAL:
119-
assert self.bit_signaling_service is not None
120-
self.bit_signaling_service.on_must_signal(feature)
121-
122116
# We cache the just calculated state of the current block _without saving it_, as it may still be unverified,
123117
# so we cannot persist its metadata. That's why we cache and save the previous boundary block above.
124118
block.set_feature_state(feature=feature, state=new_state)
@@ -192,12 +186,12 @@ def _calculate_new_state(
192186
if previous_state is FeatureState.FAILED:
193187
return FeatureState.FAILED
194188

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

197-
def get_bits_description(self, *, block: 'Block') -> dict[Feature, FeatureDescription]:
191+
def get_feature_infos(self, *, block: 'Block') -> dict[Feature, FeatureInfo]:
198192
"""Returns the criteria definition and feature state for all features at a certain block."""
199193
return {
200-
feature: FeatureDescription(
194+
feature: FeatureInfo(
201195
criteria=criteria,
202196
state=self.get_state(block=block, feature=feature)
203197
)
@@ -223,9 +217,11 @@ def _get_ancestor_at_height(self, *, block: 'Block', ancestor_height: int) -> 'B
223217
if parent_block.static_metadata.height == ancestor_height:
224218
return parent_block
225219

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)
220+
if not parent_metadata.voided_by:
221+
ancestor = self._tx_storage.get_block_by_height(ancestor_height)
222+
assert ancestor is not None, (
223+
'it is guaranteed that the ancestor of a fully connected and non-voided block is in the height index'
224+
)
229225
return ancestor
230226

231227
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',

0 commit comments

Comments
 (0)