Skip to content

Commit c070320

Browse files
committed
review changes 3
1 parent e8e9a08 commit c070320

File tree

5 files changed

+78
-38
lines changed

5 files changed

+78
-38
lines changed

hathor/feature_activation/feature_service.py

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

15-
from typing import TYPE_CHECKING
15+
from dataclasses import dataclass
16+
from typing import TYPE_CHECKING, TypeAlias
1617

1718
from hathor.feature_activation.feature import Feature
1819
from hathor.feature_activation.model.feature_description import FeatureDescription
@@ -24,6 +25,21 @@
2425
from hathor.transaction.storage import TransactionStorage
2526

2627

28+
@dataclass(frozen=True, slots=True)
29+
class BlockIsSignaling:
30+
"""Represent that a block is correctly signaling support for all currently mandatory features."""
31+
pass
32+
33+
34+
@dataclass(frozen=True, slots=True)
35+
class BlockIsMissingSignal:
36+
"""Represent that a block is not signaling support for at least one currently mandatory feature."""
37+
feature: Feature
38+
39+
40+
BlockSignalingState: TypeAlias = BlockIsSignaling | BlockIsMissingSignal
41+
42+
2743
class FeatureService:
2844
__slots__ = ('_feature_settings', '_tx_storage')
2945

@@ -37,7 +53,7 @@ def is_feature_active(self, *, block: 'Block', feature: Feature) -> bool:
3753

3854
return state == FeatureState.ACTIVE
3955

40-
def is_signaling_mandatory_features(self, block: 'Block') -> bool:
56+
def is_signaling_mandatory_features(self, block: 'Block') -> BlockSignalingState:
4157
"""
4258
Return whether a block is signaling features that are mandatory, that is, any feature currently in the
4359
MUST_SIGNAL phase.
@@ -60,9 +76,9 @@ def is_signaling_mandatory_features(self, block: 'Block') -> bool:
6076
missing_signals = threshold - count
6177

6278
if missing_signals > remaining_blocks:
63-
return False
79+
return BlockIsMissingSignal(feature=feature)
6480

65-
return True
81+
return BlockIsSignaling()
6682

6783
def get_state(self, *, block: 'Block', feature: Feature) -> FeatureState:
6884
"""Returns the state of a feature at a certain block. Uses block metadata to cache states."""

hathor/transaction/transaction_metadata.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,8 @@ class TransactionMetadata:
5353
feature_activation_bit_counts: Optional[list[int]]
5454

5555
# A dict of features in the feature activation process and their respective state. Must only be used by Blocks,
56-
# is None otherwise.
56+
# is None otherwise. This is only used for caching, so it can be safely cleared up, as it would be recalculated
57+
# when necessary.
5758
feature_states: Optional[dict[Feature, FeatureState]] = None
5859
# It must be a weakref.
5960
_tx_ref: Optional['ReferenceType[BaseTransaction]']

hathor/verification/block_verifier.py

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

1515
from hathor import daa
1616
from hathor.conf.settings import HathorSettings
17-
from hathor.feature_activation.feature_service import FeatureService
17+
from hathor.feature_activation.feature_service import BlockIsMissingSignal, BlockIsSignaling, FeatureService
1818
from hathor.profiler import get_cpu_profiler
1919
from hathor.transaction import BaseTransaction, Block
2020
from hathor.transaction.exceptions import (
@@ -125,5 +125,15 @@ def verify_mandatory_signaling(self, block: Block) -> None:
125125

126126
assert self._feature_service is not None
127127

128-
if not self._feature_service.is_signaling_mandatory_features(block):
129-
raise BlockMustSignalError()
128+
signaling_state = self._feature_service.is_signaling_mandatory_features(block)
129+
130+
match signaling_state:
131+
case BlockIsSignaling():
132+
return
133+
case BlockIsMissingSignal(feature):
134+
raise BlockMustSignalError(
135+
f"Block must signal support for feature '{feature.value}' during MUST_SIGNAL phase."
136+
)
137+
case _:
138+
# TODO: This will be changed to assert_never() so mypy can check it.
139+
raise NotImplementedError

tests/feature_activation/test_feature_service.py

+30-25
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,12 @@
1919

2020
from hathor.conf import HathorSettings
2121
from hathor.feature_activation.feature import Feature
22-
from hathor.feature_activation.feature_service import FeatureService
22+
from hathor.feature_activation.feature_service import (
23+
BlockIsMissingSignal,
24+
BlockIsSignaling,
25+
BlockSignalingState,
26+
FeatureService,
27+
)
2328
from hathor.feature_activation.model.criteria import Criteria
2429
from hathor.feature_activation.model.feature_description import FeatureDescription
2530
from hathor.feature_activation.model.feature_state import FeatureState
@@ -655,29 +660,29 @@ def test_get_ancestor_at_height_voided(
655660

656661

657662
@pytest.mark.parametrize(
658-
['bit', 'threshold', 'block_height', 'is_signaling'],
663+
['bit', 'threshold', 'block_height', 'signaling_state'],
659664
[
660-
(0, 4, 0, True),
661-
(0, 4, 3, True),
662-
(0, 4, 7, True),
663-
(0, 4, 8, True),
664-
(0, 4, 11, True),
665-
(0, 4, 12, True),
666-
667-
(1, 4, 0, True),
668-
(1, 4, 3, True),
669-
(1, 4, 7, True),
670-
(1, 4, 8, True),
671-
(1, 4, 9, True),
672-
(1, 4, 10, False),
673-
(1, 4, 11, False),
674-
(1, 4, 12, True),
675-
676-
(2, 2, 8, True),
677-
(2, 2, 9, True),
678-
(2, 2, 10, True),
679-
(2, 2, 11, False),
680-
(2, 2, 12, True),
665+
(0, 4, 0, BlockIsSignaling()),
666+
(0, 4, 3, BlockIsSignaling()),
667+
(0, 4, 7, BlockIsSignaling()),
668+
(0, 4, 8, BlockIsSignaling()),
669+
(0, 4, 11, BlockIsSignaling()),
670+
(0, 4, 12, BlockIsSignaling()),
671+
672+
(1, 4, 0, BlockIsSignaling()),
673+
(1, 4, 3, BlockIsSignaling()),
674+
(1, 4, 7, BlockIsSignaling()),
675+
(1, 4, 8, BlockIsSignaling()),
676+
(1, 4, 9, BlockIsSignaling()),
677+
(1, 4, 10, BlockIsMissingSignal(feature=Feature.NOP_FEATURE_1)),
678+
(1, 4, 11, BlockIsMissingSignal(feature=Feature.NOP_FEATURE_1)),
679+
(1, 4, 12, BlockIsSignaling()),
680+
681+
(2, 2, 8, BlockIsSignaling()),
682+
(2, 2, 9, BlockIsSignaling()),
683+
(2, 2, 10, BlockIsSignaling()),
684+
(2, 2, 11, BlockIsMissingSignal(feature=Feature.NOP_FEATURE_1)),
685+
(2, 2, 12, BlockIsSignaling()),
681686
]
682687
)
683688
def test_check_must_signal(
@@ -686,7 +691,7 @@ def test_check_must_signal(
686691
bit: int,
687692
threshold: int,
688693
block_height: int,
689-
is_signaling: bool
694+
signaling_state: BlockSignalingState
690695
) -> None:
691696
feature_settings = FeatureSettings(
692697
evaluation_interval=4,
@@ -706,4 +711,4 @@ def test_check_must_signal(
706711

707712
result = service.is_signaling_mandatory_features(block)
708713

709-
assert result is is_signaling
714+
assert result == signaling_state

tests/tx/test_block.py

+13-5
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@
1818

1919
from hathor.conf.get_settings import get_settings
2020
from hathor.conf.settings import HathorSettings
21-
from hathor.feature_activation.feature_service import FeatureService
21+
from hathor.feature_activation.feature import Feature
22+
from hathor.feature_activation.feature_service import BlockIsMissingSignal, BlockIsSignaling, FeatureService
2223
from hathor.transaction import Block, TransactionMetadata
2324
from hathor.transaction.exceptions import BlockMustSignalError
2425
from hathor.transaction.storage import TransactionMemoryStorage, TransactionStorage
@@ -137,7 +138,10 @@ def test_get_feature_activation_bit_value() -> None:
137138
assert block.get_feature_activation_bit_value(3) == 0
138139

139140

140-
@pytest.mark.parametrize('is_signaling_mandatory_features', [False, True])
141+
@pytest.mark.parametrize(
142+
'is_signaling_mandatory_features',
143+
[BlockIsSignaling(), BlockIsMissingSignal(feature=Feature.NOP_FEATURE_1)]
144+
)
141145
def test_verify_must_signal_when_feature_activation_is_disabled(is_signaling_mandatory_features: bool) -> None:
142146
settings = Mock(spec_set=HathorSettings)
143147
settings.FEATURE_ACTIVATION.enable_usage = False
@@ -153,19 +157,23 @@ def test_verify_must_signal() -> None:
153157
settings = Mock(spec_set=HathorSettings)
154158
settings.FEATURE_ACTIVATION.enable_usage = True
155159
feature_service = Mock(spec_set=FeatureService)
156-
feature_service.is_signaling_mandatory_features = Mock(return_value=False)
160+
feature_service.is_signaling_mandatory_features = Mock(
161+
return_value=BlockIsMissingSignal(feature=Feature.NOP_FEATURE_1)
162+
)
157163
verifier = BlockVerifier(settings=settings, feature_service=feature_service)
158164
block = Block()
159165

160-
with pytest.raises(BlockMustSignalError):
166+
with pytest.raises(BlockMustSignalError) as e:
161167
verifier.verify_mandatory_signaling(block)
162168

169+
assert str(e.value) == "Block must signal support for feature 'NOP_FEATURE_1' during MUST_SIGNAL phase."
170+
163171

164172
def test_verify_must_not_signal() -> None:
165173
settings = Mock(spec_set=HathorSettings)
166174
settings.FEATURE_ACTIVATION.enable_usage = True
167175
feature_service = Mock(spec_set=FeatureService)
168-
feature_service.is_signaling_mandatory_features = Mock(return_value=True)
176+
feature_service.is_signaling_mandatory_features = Mock(return_value=BlockIsSignaling())
169177
verifier = BlockVerifier(settings=settings, feature_service=feature_service)
170178
block = Block()
171179

0 commit comments

Comments
 (0)