Skip to content

Commit cbc3dc1

Browse files
committed
refactor(feature-activation): improve usage check and signal verification
1 parent 7eab19b commit cbc3dc1

File tree

12 files changed

+87
-85
lines changed

12 files changed

+87
-85
lines changed

hathor/builder/builder.py

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ class BuildArtifacts(NamedTuple):
7474

7575

7676
_VertexVerifiersBuilder: TypeAlias = Callable[
77-
[HathorSettingsType, DifficultyAdjustmentAlgorithm, FeatureService],
77+
[HathorSettingsType, DifficultyAdjustmentAlgorithm],
7878
VertexVerifiers
7979
]
8080

@@ -473,24 +473,20 @@ def _get_or_create_bit_signaling_service(self) -> BitSignalingService:
473473
def _get_or_create_verification_service(self) -> VerificationService:
474474
if self._verification_service is None:
475475
verifiers = self._get_or_create_vertex_verifiers()
476-
self._verification_service = VerificationService(verifiers=verifiers)
476+
feature_service = self._get_or_create_feature_service()
477+
self._verification_service = VerificationService(verifiers=verifiers, feature_service=feature_service)
477478

478479
return self._verification_service
479480

480481
def _get_or_create_vertex_verifiers(self) -> VertexVerifiers:
481482
if self._vertex_verifiers is None:
482483
settings = self._get_or_create_settings()
483-
feature_service = self._get_or_create_feature_service()
484484
daa = self._get_or_create_daa()
485485

486486
if self._vertex_verifiers_builder:
487-
self._vertex_verifiers = self._vertex_verifiers_builder(settings, daa, feature_service)
487+
self._vertex_verifiers = self._vertex_verifiers_builder(settings, daa)
488488
else:
489-
self._vertex_verifiers = VertexVerifiers.create_defaults(
490-
settings=settings,
491-
daa=daa,
492-
feature_service=feature_service,
493-
)
489+
self._vertex_verifiers = VertexVerifiers.create_defaults(settings=settings, daa=daa)
494490

495491
return self._vertex_verifiers
496492

hathor/builder/cli_builder.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -222,12 +222,8 @@ def create_manager(self, reactor: Reactor) -> HathorManager:
222222

223223
daa = DifficultyAdjustmentAlgorithm(settings=settings, test_mode=test_mode)
224224

225-
vertex_verifiers = VertexVerifiers.create_defaults(
226-
settings=settings,
227-
daa=daa,
228-
feature_service=self.feature_service
229-
)
230-
verification_service = VerificationService(verifiers=vertex_verifiers)
225+
vertex_verifiers = VertexVerifiers.create_defaults(settings=settings, daa=daa)
226+
verification_service = VerificationService(verifiers=vertex_verifiers, feature_service=self.feature_service)
231227

232228
cpu_mining_service = CpuMiningService()
233229

hathor/feature_activation/bit_signaling_service.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,9 @@ def start(self) -> None:
5858
Log information related to bit signaling. Must be called after the storage is ready and migrations have
5959
been applied.
6060
"""
61+
if not self._feature_service.is_usage_enabled():
62+
return
63+
6164
best_block = self._tx_storage.get_best_block()
6265

6366
self._warn_non_signaling_features(best_block)
@@ -74,6 +77,9 @@ def generate_signal_bits(self, *, block: Block, log: bool = False) -> int:
7477
7578
Returns: a number that represents the signal bits in binary.
7679
"""
80+
if not self._feature_service.is_usage_enabled():
81+
return 0
82+
7783
signaling_features = self._get_signaling_features(block)
7884
signal_bits = 0
7985

hathor/feature_activation/feature_service.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,14 @@
2525
from hathor.transaction.storage import TransactionStorage
2626

2727

28+
@dataclass(frozen=True, slots=True)
29+
class FeatureActivationIsDisabled:
30+
"""Represent that Feature Activation is disabled and therefore the block signaling state cannot be calculated."""
31+
32+
2833
@dataclass(frozen=True, slots=True)
2934
class BlockIsSignaling:
3035
"""Represent that a block is correctly signaling support for all currently mandatory features."""
31-
pass
3236

3337

3438
@dataclass(frozen=True, slots=True)
@@ -37,7 +41,7 @@ class BlockIsMissingSignal:
3741
feature: Feature
3842

3943

40-
BlockSignalingState: TypeAlias = BlockIsSignaling | BlockIsMissingSignal
44+
BlockSignalingState: TypeAlias = FeatureActivationIsDisabled | BlockIsSignaling | BlockIsMissingSignal
4145

4246

4347
class FeatureService:
@@ -47,8 +51,13 @@ def __init__(self, *, feature_settings: FeatureSettings, tx_storage: 'Transactio
4751
self._feature_settings = feature_settings
4852
self._tx_storage = tx_storage
4953

54+
def is_usage_enabled(self) -> bool:
55+
"""Return whether Feature Activation usage is enabled."""
56+
return self._feature_settings.enable_usage
57+
5058
def is_feature_active(self, *, block: 'Block', feature: Feature) -> bool:
5159
"""Returns whether a Feature is active at a certain block."""
60+
assert self.is_usage_enabled()
5261
state = self.get_state(block=block, feature=feature)
5362

5463
return state == FeatureState.ACTIVE
@@ -58,6 +67,9 @@ def is_signaling_mandatory_features(self, block: 'Block') -> BlockSignalingState
5867
Return whether a block is signaling features that are mandatory, that is, any feature currently in the
5968
MUST_SIGNAL phase.
6069
"""
70+
if not self.is_usage_enabled():
71+
return FeatureActivationIsDisabled()
72+
6173
bit_counts = block.get_feature_activation_bit_counts()
6274
height = block.get_height()
6375
offset_to_boundary = height % self._feature_settings.evaluation_interval
@@ -82,6 +94,7 @@ def is_signaling_mandatory_features(self, block: 'Block') -> BlockSignalingState
8294

8395
def get_state(self, *, block: 'Block', feature: Feature) -> FeatureState:
8496
"""Returns the state of a feature at a certain block. Uses block metadata to cache states."""
97+
assert self.is_usage_enabled()
8598

8699
# per definition, the genesis block is in the DEFINED state for all features
87100
if block.is_genesis:
@@ -190,6 +203,7 @@ def _calculate_new_state(
190203

191204
def get_bits_description(self, *, block: 'Block') -> dict[Feature, FeatureDescription]:
192205
"""Returns the criteria definition and feature state for all features at a certain block."""
206+
assert self.is_usage_enabled()
193207
return {
194208
feature: FeatureDescription(
195209
criteria=criteria,

hathor/manager.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1061,7 +1061,7 @@ def tx_fully_validated(self, tx: BaseTransaction, *, quiet: bool) -> None:
10611061

10621062
def _log_feature_states(self, vertex: BaseTransaction) -> None:
10631063
"""Log features states for a block. Used as part of the Feature Activation Phased Testing."""
1064-
if not self._settings.FEATURE_ACTIVATION.enable_usage or not isinstance(vertex, Block):
1064+
if not self._feature_service.is_usage_enabled() or not isinstance(vertex, Block):
10651065
return
10661066

10671067
feature_descriptions = self._feature_service.get_bits_description(block=vertex)

hathor/simulator/simulator.py

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
from hathor.conf.get_settings import get_settings
2525
from hathor.conf.settings import HathorSettings
2626
from hathor.daa import DifficultyAdjustmentAlgorithm
27-
from hathor.feature_activation.feature_service import FeatureService
2827
from hathor.manager import HathorManager
2928
from hathor.p2p.peer_id import PeerId
3029
from hathor.simulator.clock import HeapClock, MemoryReactorHeapClock
@@ -243,17 +242,12 @@ def run(self,
243242
return True
244243

245244

246-
def _build_vertex_verifiers(
247-
settings: HathorSettings,
248-
daa: DifficultyAdjustmentAlgorithm,
249-
feature_service: FeatureService
250-
) -> VertexVerifiers:
245+
def _build_vertex_verifiers(settings: HathorSettings, daa: DifficultyAdjustmentAlgorithm) -> VertexVerifiers:
251246
"""
252247
A custom VertexVerifiers builder to be used by the simulator.
253248
"""
254249
return VertexVerifiers.create(
255250
settings=settings,
256251
vertex_verifier=SimulatorVertexVerifier(settings=settings, daa=daa),
257252
daa=daa,
258-
feature_service=feature_service,
259253
)

hathor/verification/block_verifier.py

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,16 @@
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
17-
from hathor.feature_activation.feature_service import BlockIsMissingSignal, BlockIsSignaling, FeatureService
19+
from hathor.feature_activation.feature_service import (
20+
BlockIsMissingSignal,
21+
BlockIsSignaling,
22+
BlockSignalingState,
23+
FeatureActivationIsDisabled,
24+
)
1825
from hathor.transaction import Block
1926
from hathor.transaction.exceptions import (
2027
BlockMustSignalError,
@@ -28,18 +35,16 @@
2835

2936

3037
class BlockVerifier:
31-
__slots__ = ('_settings', '_daa', '_feature_service')
38+
__slots__ = ('_settings', '_daa')
3239

3340
def __init__(
3441
self,
3542
*,
3643
settings: HathorSettings,
3744
daa: DifficultyAdjustmentAlgorithm,
38-
feature_service: FeatureService | None = None
3945
) -> None:
4046
self._settings = settings
4147
self._daa = daa
42-
self._feature_service = feature_service
4348

4449
def verify_height(self, block: Block) -> None:
4550
"""Validate that the block height is enough to confirm all transactions being confirmed."""
@@ -80,22 +85,14 @@ def verify_data(self, block: Block) -> None:
8085
if len(block.data) > self._settings.BLOCK_DATA_MAX_SIZE:
8186
raise TransactionDataError('block data has {} bytes'.format(len(block.data)))
8287

83-
def verify_mandatory_signaling(self, block: Block) -> None:
88+
def verify_mandatory_signaling(self, signaling_state: BlockSignalingState) -> None:
8489
"""Verify whether this block is missing mandatory signaling for any feature."""
85-
if not self._settings.FEATURE_ACTIVATION.enable_usage:
86-
return
87-
88-
assert self._feature_service is not None
89-
90-
signaling_state = self._feature_service.is_signaling_mandatory_features(block)
91-
9290
match signaling_state:
93-
case BlockIsSignaling():
91+
case FeatureActivationIsDisabled() | BlockIsSignaling():
9492
return
9593
case BlockIsMissingSignal(feature):
9694
raise BlockMustSignalError(
9795
f"Block must signal support for feature '{feature.value}' during MUST_SIGNAL phase."
9896
)
9997
case _:
100-
# TODO: This will be changed to assert_never() so mypy can check it.
101-
raise NotImplementedError
98+
assert_never(signaling_state)

hathor/verification/verification_service.py

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
from typing_extensions import assert_never
1616

17+
from hathor.feature_activation.feature_service import BlockSignalingState, FeatureService
1718
from hathor.profiler import get_cpu_profiler
1819
from hathor.transaction import BaseTransaction, Block, MergeMinedBlock, Transaction, TxVersion
1920
from hathor.transaction.token_creation_tx import TokenCreationTransaction
@@ -26,10 +27,11 @@
2627

2728

2829
class VerificationService:
29-
__slots__ = ('verifiers', )
30+
__slots__ = ('verifiers', '_feature_service')
3031

31-
def __init__(self, *, verifiers: VertexVerifiers) -> None:
32+
def __init__(self, *, verifiers: VertexVerifiers, feature_service: FeatureService | None = None) -> None:
3233
self.verifiers = verifiers
34+
self._feature_service = feature_service
3335

3436
def validate_basic(self, vertex: BaseTransaction, *, skip_block_weight_verification: bool = False) -> bool:
3537
""" Run basic validations (all that are possible without dependencies) and update the validation state.
@@ -124,14 +126,17 @@ def verify(self, vertex: BaseTransaction, *, reject_locked_reward: bool = True)
124126
"""Run all verifications. Raises on error.
125127
126128
Used by `self.validate_full`. Should not modify the validation state."""
129+
assert self._feature_service is not None
127130
# We assert with type() instead of isinstance() because each subclass has a specific branch.
128131
match vertex.version:
129132
case TxVersion.REGULAR_BLOCK:
130133
assert type(vertex) is Block
131-
self._verify_block(vertex)
134+
signaling_state = self._feature_service.is_signaling_mandatory_features(vertex)
135+
self._verify_block(vertex, signaling_state)
132136
case TxVersion.MERGE_MINED_BLOCK:
133137
assert type(vertex) is MergeMinedBlock
134-
self._verify_merge_mined_block(vertex)
138+
signaling_state = self._feature_service.is_signaling_mandatory_features(vertex)
139+
self._verify_merge_mined_block(vertex, signaling_state)
135140
case TxVersion.REGULAR_TRANSACTION:
136141
assert type(vertex) is Transaction
137142
self._verify_tx(vertex, reject_locked_reward=reject_locked_reward)
@@ -142,7 +147,7 @@ def verify(self, vertex: BaseTransaction, *, reject_locked_reward: bool = True)
142147
assert_never(vertex.version)
143148

144149
@cpu.profiler(key=lambda _, block: 'block-verify!{}'.format(block.hash.hex()))
145-
def _verify_block(self, block: Block) -> None:
150+
def _verify_block(self, block: Block, signaling_state: BlockSignalingState) -> None:
146151
"""
147152
(1) confirms at least two pending transactions and references last block
148153
(2) solves the pow with the correct weight (done in HathorManager)
@@ -163,10 +168,10 @@ def _verify_block(self, block: Block) -> None:
163168

164169
self.verifiers.block.verify_height(block)
165170

166-
self.verifiers.block.verify_mandatory_signaling(block)
171+
self.verifiers.block.verify_mandatory_signaling(signaling_state)
167172

168-
def _verify_merge_mined_block(self, block: MergeMinedBlock) -> None:
169-
self._verify_block(block)
173+
def _verify_merge_mined_block(self, block: MergeMinedBlock, signaling_state: BlockSignalingState) -> None:
174+
self._verify_block(block, signaling_state)
170175

171176
@cpu.profiler(key=lambda _, tx: 'tx-verify!{}'.format(tx.hash.hex()))
172177
def _verify_tx(

hathor/verification/vertex_verifiers.py

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616

1717
from hathor.conf.settings import HathorSettings
1818
from hathor.daa import DifficultyAdjustmentAlgorithm
19-
from hathor.feature_activation.feature_service import FeatureService
2019
from hathor.verification.block_verifier import BlockVerifier
2120
from hathor.verification.merge_mined_block_verifier import MergeMinedBlockVerifier
2221
from hathor.verification.token_creation_transaction_verifier import TokenCreationTransactionVerifier
@@ -33,13 +32,7 @@ class VertexVerifiers(NamedTuple):
3332
token_creation_tx: TokenCreationTransactionVerifier
3433

3534
@classmethod
36-
def create_defaults(
37-
cls,
38-
*,
39-
settings: HathorSettings,
40-
daa: DifficultyAdjustmentAlgorithm,
41-
feature_service: FeatureService | None = None,
42-
) -> 'VertexVerifiers':
35+
def create_defaults(cls, *, settings: HathorSettings, daa: DifficultyAdjustmentAlgorithm) -> 'VertexVerifiers':
4336
"""
4437
Create a VertexVerifiers instance using the default verifier for each vertex type,
4538
from all required dependencies.
@@ -50,7 +43,6 @@ def create_defaults(
5043
settings=settings,
5144
vertex_verifier=vertex_verifier,
5245
daa=daa,
53-
feature_service=feature_service
5446
)
5547

5648
@classmethod
@@ -60,12 +52,11 @@ def create(
6052
settings: HathorSettings,
6153
vertex_verifier: VertexVerifier,
6254
daa: DifficultyAdjustmentAlgorithm,
63-
feature_service: FeatureService | None = None,
6455
) -> 'VertexVerifiers':
6556
"""
6657
Create a VertexVerifiers instance using a custom vertex_verifier.
6758
"""
68-
block_verifier = BlockVerifier(settings=settings, daa=daa, feature_service=feature_service)
59+
block_verifier = BlockVerifier(settings=settings, daa=daa)
6960
merge_mined_block_verifier = MergeMinedBlockVerifier()
7061
tx_verifier = TransactionVerifier(settings=settings, daa=daa)
7162
token_creation_tx_verifier = TokenCreationTransactionVerifier(settings=settings)

0 commit comments

Comments
 (0)