Skip to content

Commit 3381848

Browse files
committed
refactor(metadata): pre-refactors for migrating feature states to static metadata [part 1/2]
1 parent a43cb95 commit 3381848

23 files changed

+306
-367
lines changed

hathor/builder/builder.py

+12-8
Original file line numberDiff line numberDiff line change
@@ -435,6 +435,7 @@ def _get_or_create_indexes_manager(self) -> IndexesManager:
435435

436436
def _get_or_create_tx_storage(self) -> TransactionStorage:
437437
indexes = self._get_or_create_indexes_manager()
438+
settings = self._get_or_create_settings()
438439

439440
if self._tx_storage is not None:
440441
# If a tx storage is provided, set the indexes manager to it.
@@ -446,11 +447,11 @@ def _get_or_create_tx_storage(self) -> TransactionStorage:
446447
store_indexes = None
447448

448449
if self._storage_type == StorageType.MEMORY:
449-
self._tx_storage = TransactionMemoryStorage(indexes=store_indexes)
450+
self._tx_storage = TransactionMemoryStorage(indexes=store_indexes, settings=settings)
450451

451452
elif self._storage_type == StorageType.ROCKSDB:
452453
rocksdb_storage = self._get_or_create_rocksdb_storage()
453-
self._tx_storage = TransactionRocksDBStorage(rocksdb_storage, indexes=store_indexes)
454+
self._tx_storage = TransactionRocksDBStorage(rocksdb_storage, indexes=store_indexes, settings=settings)
454455

455456
else:
456457
raise NotImplementedError
@@ -460,7 +461,13 @@ def _get_or_create_tx_storage(self) -> TransactionStorage:
460461
kwargs: dict[str, Any] = {}
461462
if self._tx_storage_cache_capacity is not None:
462463
kwargs['capacity'] = self._tx_storage_cache_capacity
463-
self._tx_storage = TransactionCacheStorage(self._tx_storage, reactor, indexes=indexes, **kwargs)
464+
self._tx_storage = TransactionCacheStorage(
465+
self._tx_storage,
466+
reactor,
467+
indexes=indexes,
468+
settings=settings,
469+
**kwargs
470+
)
464471

465472
return self._tx_storage
466473

@@ -504,10 +511,7 @@ def _get_or_create_feature_service(self) -> FeatureService:
504511
if self._feature_service is None:
505512
settings = self._get_or_create_settings()
506513
tx_storage = self._get_or_create_tx_storage()
507-
self._feature_service = FeatureService(
508-
feature_settings=settings.FEATURE_ACTIVATION,
509-
tx_storage=tx_storage
510-
)
514+
self._feature_service = FeatureService(settings=settings, tx_storage=tx_storage)
511515

512516
return self._feature_service
513517

@@ -518,7 +522,7 @@ def _get_or_create_bit_signaling_service(self) -> BitSignalingService:
518522
feature_service = self._get_or_create_feature_service()
519523
feature_storage = self._get_or_create_feature_storage()
520524
self._bit_signaling_service = BitSignalingService(
521-
feature_settings=settings.FEATURE_ACTIVATION,
525+
settings=settings,
522526
feature_service=feature_service,
523527
tx_storage=tx_storage,
524528
support_features=self._support_features,

hathor/builder/cli_builder.py

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

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

260-
self.feature_service = FeatureService(
261-
feature_settings=settings.FEATURE_ACTIVATION,
262-
tx_storage=tx_storage
263-
)
260+
self.feature_service = FeatureService(settings=settings, tx_storage=tx_storage)
264261

265262
bit_signaling_service = BitSignalingService(
266-
feature_settings=settings.FEATURE_ACTIVATION,
263+
settings=settings,
267264
feature_service=self.feature_service,
268265
tx_storage=tx_storage,
269266
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-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

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

15-
import dataclasses
1615
from abc import ABC
17-
from dataclasses import dataclass
1816
from itertools import chain, starmap, zip_longest
1917
from operator import add
2018
from typing import TYPE_CHECKING, Callable
@@ -25,15 +23,15 @@
2523
from hathor.feature_activation.feature import Feature
2624
from hathor.feature_activation.model.feature_state import FeatureState
2725
from hathor.types import VertexId
28-
from hathor.util import json_dumpb, json_loadb
26+
from hathor.util import json_loadb
27+
from hathor.utils.pydantic import BaseModel
2928

3029
if TYPE_CHECKING:
3130
from hathor.transaction import BaseTransaction, Block, Transaction
3231
from hathor.transaction.storage import TransactionStorage
3332

3433

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

50-
def to_bytes(self) -> bytes:
51-
"""Convert this static metadata instance to a json bytes representation."""
52-
return json_dumpb(dataclasses.asdict(self))
53-
5448
@classmethod
5549
def from_bytes(cls, data: bytes, *, target: 'BaseTransaction') -> 'VertexStaticMetadata':
5650
"""Create a static metadata instance from a json bytes representation, with a known vertex type target."""
@@ -66,11 +60,10 @@ def from_bytes(cls, data: bytes, *, target: 'BaseTransaction') -> 'VertexStaticM
6660
raise NotImplementedError
6761

6862

69-
@dataclass(slots=True, frozen=True, kw_only=True)
7063
class BlockStaticMetadata(VertexStaticMetadata):
7164
height: int
7265

73-
# A list of feature activation bit counts. Must only be used by Blocks, is None otherwise.
66+
# A list of feature activation bit counts.
7467
# Each list index corresponds to a bit position, and its respective value is the rolling count of active bits from
7568
# the previous boundary block up to this block, including it. LSB is on the left.
7669
feature_activation_bit_counts: list[int]
@@ -178,7 +171,6 @@ def _get_previous_feature_activation_bit_counts(
178171
return parent_block.static_metadata.feature_activation_bit_counts
179172

180173

181-
@dataclass(slots=True, frozen=True, kw_only=True)
182174
class TransactionStaticMetadata(VertexStaticMetadata):
183175
@classmethod
184176
def create_from_storage(cls, tx: 'Transaction', settings: HathorSettings, storage: 'TransactionStorage') -> Self:

0 commit comments

Comments
 (0)