Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(verification): move feature activation dependency #896

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 5 additions & 9 deletions hathor/builder/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ class BuildArtifacts(NamedTuple):


_VertexVerifiersBuilder: TypeAlias = Callable[
[HathorSettingsType, DifficultyAdjustmentAlgorithm, FeatureService],
msbrogli marked this conversation as resolved.
Show resolved Hide resolved
[HathorSettingsType, DifficultyAdjustmentAlgorithm],
VertexVerifiers
]

Expand Down Expand Up @@ -509,7 +509,8 @@ def _get_or_create_bit_signaling_service(self) -> BitSignalingService:
def _get_or_create_verification_service(self) -> VerificationService:
if self._verification_service is None:
verifiers = self._get_or_create_vertex_verifiers()
self._verification_service = VerificationService(verifiers=verifiers)
feature_service = self._get_or_create_feature_service()
self._verification_service = VerificationService(verifiers=verifiers, feature_service=feature_service)

return self._verification_service

Expand All @@ -525,17 +526,12 @@ def _get_or_create_feature_storage(self) -> FeatureActivationStorage | None:
def _get_or_create_vertex_verifiers(self) -> VertexVerifiers:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not simply verify whether it is enabled or not here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This has been removed from this PR.

if self._vertex_verifiers is None:
settings = self._get_or_create_settings()
feature_service = self._get_or_create_feature_service()
daa = self._get_or_create_daa()

if self._vertex_verifiers_builder:
self._vertex_verifiers = self._vertex_verifiers_builder(settings, daa, feature_service)
self._vertex_verifiers = self._vertex_verifiers_builder(settings, daa)
else:
self._vertex_verifiers = VertexVerifiers.create_defaults(
settings=settings,
daa=daa,
feature_service=feature_service,
)
self._vertex_verifiers = VertexVerifiers.create_defaults(settings=settings, daa=daa)

return self._vertex_verifiers

Expand Down
8 changes: 2 additions & 6 deletions hathor/builder/cli_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -275,12 +275,8 @@ def create_manager(self, reactor: Reactor) -> HathorManager:

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

vertex_verifiers = VertexVerifiers.create_defaults(
settings=settings,
daa=daa,
feature_service=self.feature_service
)
verification_service = VerificationService(verifiers=vertex_verifiers)
vertex_verifiers = VertexVerifiers.create_defaults(settings=settings, daa=daa)
verification_service = VerificationService(verifiers=vertex_verifiers, feature_service=self.feature_service)

cpu_mining_service = CpuMiningService()

Expand Down
4 changes: 1 addition & 3 deletions hathor/cli/mining.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,15 +133,13 @@ def execute(args: Namespace) -> None:
block.nonce, block.weight))

try:
from unittest.mock import Mock

from hathor.conf.get_settings import get_global_settings
from hathor.daa import DifficultyAdjustmentAlgorithm
from hathor.verification.verification_service import VerificationService
from hathor.verification.vertex_verifiers import VertexVerifiers
settings = get_global_settings()
daa = DifficultyAdjustmentAlgorithm(settings=settings)
verifiers = VertexVerifiers.create_defaults(settings=settings, daa=daa, feature_service=Mock())
verifiers = VertexVerifiers.create_defaults(settings=settings, daa=daa)
verification_service = VerificationService(verifiers=verifiers)
verification_service.verify_without_storage(block)
except HathorError:
Expand Down
2 changes: 1 addition & 1 deletion hathor/feature_activation/bit_signaling_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ def _log_signal_bits(self, feature: Feature, enable_bit: bool, support: bool, no

def _get_signaling_features(self, block: Block) -> dict[Feature, Criteria]:
"""Given a specific block, return all features that are in a signaling state for that block."""
feature_descriptions = self._feature_service.get_bits_description(block=block)
feature_descriptions = self._feature_service.get_feature_info(block=block)
signaling_features = {
feature: description.criteria
for feature, description in feature_descriptions.items()
Expand Down
8 changes: 4 additions & 4 deletions hathor/feature_activation/feature_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from typing import TYPE_CHECKING, Optional, TypeAlias

from hathor.feature_activation.feature import Feature
from hathor.feature_activation.model.feature_description import FeatureDescription
from hathor.feature_activation.model.feature_description import FeatureInfo
from hathor.feature_activation.model.feature_state import FeatureState
from hathor.feature_activation.settings import Settings as FeatureSettings

Expand Down Expand Up @@ -64,7 +64,7 @@ def is_signaling_mandatory_features(self, block: 'Block') -> BlockSignalingState
height = block.get_height()
offset_to_boundary = height % self._feature_settings.evaluation_interval
remaining_blocks = self._feature_settings.evaluation_interval - offset_to_boundary - 1
descriptions = self.get_bits_description(block=block)
descriptions = self.get_feature_info(block=block)

must_signal_features = (
feature for feature, description in descriptions.items()
Expand Down Expand Up @@ -194,10 +194,10 @@ def _calculate_new_state(

raise ValueError(f'Unknown previous state: {previous_state}')

def get_bits_description(self, *, block: 'Block') -> dict[Feature, FeatureDescription]:
def get_feature_info(self, *, block: 'Block') -> dict[Feature, FeatureInfo]:
"""Returns the criteria definition and feature state for all features at a certain block."""
return {
feature: FeatureDescription(
feature: FeatureInfo(
criteria=criteria,
state=self.get_state(block=block, feature=feature)
)
Expand Down
2 changes: 1 addition & 1 deletion hathor/feature_activation/model/feature_description.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
from hathor.feature_activation.model.feature_state import FeatureState


class FeatureDescription(NamedTuple):
class FeatureInfo(NamedTuple):
"""Represents all information related to one feature, that is, its criteria and state."""
criteria: Criteria
state: FeatureState
4 changes: 4 additions & 0 deletions hathor/feature_activation/model/feature_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,7 @@ def get_signaling_states() -> set['FeatureState']:
support it or not through bit signals is valid during those states.
"""
return {FeatureState.STARTED, FeatureState.MUST_SIGNAL, FeatureState.LOCKED_IN}

def is_active(self) -> bool:
"""Return whether the state is active."""
return self is FeatureState.ACTIVE
2 changes: 1 addition & 1 deletion hathor/feature_activation/resources/feature.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ def get_block_features(self, request: Request) -> bytes:
return error.json_dumpb()

signal_bits = []
feature_descriptions = self._feature_service.get_bits_description(block=block)
feature_descriptions = self._feature_service.get_feature_info(block=block)

for feature, description in feature_descriptions.items():
if description.state not in FeatureState.get_signaling_states():
Expand Down
8 changes: 1 addition & 7 deletions hathor/simulator/simulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
from hathor.conf.get_settings import get_global_settings
from hathor.conf.settings import HathorSettings
from hathor.daa import DifficultyAdjustmentAlgorithm
from hathor.feature_activation.feature_service import FeatureService
from hathor.manager import HathorManager
from hathor.p2p.peer_id import PeerId
from hathor.simulator.clock import HeapClock, MemoryReactorHeapClock
Expand Down Expand Up @@ -243,17 +242,12 @@ def run(self,
return True


def _build_vertex_verifiers(
settings: HathorSettings,
daa: DifficultyAdjustmentAlgorithm,
feature_service: FeatureService
) -> VertexVerifiers:
def _build_vertex_verifiers(settings: HathorSettings, daa: DifficultyAdjustmentAlgorithm) -> VertexVerifiers:
"""
A custom VertexVerifiers builder to be used by the simulator.
"""
return VertexVerifiers.create(
settings=settings,
vertex_verifier=SimulatorVertexVerifier(settings=settings, daa=daa),
daa=daa,
feature_service=feature_service,
)
15 changes: 6 additions & 9 deletions hathor/verification/block_verifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@
# See the License for the specific language governing permissions and
# limitations under the License.

from typing_extensions import assert_never

from hathor.conf.settings import HathorSettings
from hathor.daa import DifficultyAdjustmentAlgorithm
from hathor.feature_activation.feature_service import BlockIsMissingSignal, BlockIsSignaling, FeatureService
from hathor.feature_activation.feature_service import BlockIsMissingSignal, BlockIsSignaling, BlockSignalingState
from hathor.transaction import Block
from hathor.transaction.exceptions import (
BlockMustSignalError,
Expand All @@ -30,18 +32,16 @@


class BlockVerifier:
__slots__ = ('_settings', '_daa', '_feature_service')
__slots__ = ('_settings', '_daa')

def __init__(
self,
*,
settings: HathorSettings,
daa: DifficultyAdjustmentAlgorithm,
feature_service: FeatureService,
) -> None:
self._settings = settings
self._daa = daa
self._feature_service = feature_service

def verify_height(self, block: Block) -> None:
"""Validate that the block height is enough to confirm all transactions being confirmed."""
Expand Down Expand Up @@ -86,10 +86,8 @@ def verify_data(self, block: Block) -> None:
if len(block.data) > self._settings.BLOCK_DATA_MAX_SIZE:
raise TransactionDataError('block data has {} bytes'.format(len(block.data)))

def verify_mandatory_signaling(self, block: Block) -> None:
def verify_mandatory_signaling(self, signaling_state: BlockSignalingState) -> None:
"""Verify whether this block is missing mandatory signaling for any feature."""
signaling_state = self._feature_service.is_signaling_mandatory_features(block)

match signaling_state:
case BlockIsSignaling():
return
Expand All @@ -98,5 +96,4 @@ def verify_mandatory_signaling(self, block: Block) -> None:
f"Block must signal support for feature '{feature.value}' during MUST_SIGNAL phase."
)
case _:
# TODO: This will be changed to assert_never() so mypy can check it.
raise NotImplementedError
assert_never(signaling_state)
22 changes: 9 additions & 13 deletions hathor/verification/merge_mined_block_verifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,29 +14,25 @@

from hathor.conf.settings import HathorSettings
from hathor.feature_activation.feature import Feature
from hathor.feature_activation.feature_service import FeatureService
from hathor.feature_activation.model.feature_description import FeatureInfo
from hathor.transaction import MergeMinedBlock


class MergeMinedBlockVerifier:
__slots__ = ('_settings', '_feature_service',)
__slots__ = ('_settings',)

def __init__(self, *, settings: HathorSettings, feature_service: FeatureService):
def __init__(self, *, settings: HathorSettings) -> None:
self._settings = settings
self._feature_service = feature_service

def verify_aux_pow(self, block: MergeMinedBlock) -> None:
def verify_aux_pow(self, block: MergeMinedBlock, feature_info: dict[Feature, FeatureInfo]) -> None:
""" Verify auxiliary proof-of-work (for merged mining).
"""
assert block.aux_pow is not None

is_feature_active = self._feature_service.is_feature_active(
block=block,
feature=Feature.INCREASE_MAX_MERKLE_PATH_LENGTH
)
max_merkle_path_length = (
self._settings.NEW_MAX_MERKLE_PATH_LENGTH if is_feature_active
else self._settings.OLD_MAX_MERKLE_PATH_LENGTH
)
max_merkle_path_length = self._settings.OLD_MAX_MERKLE_PATH_LENGTH
merkle_path_info = feature_info.get(Feature.INCREASE_MAX_MERKLE_PATH_LENGTH)

if merkle_path_info and merkle_path_info.state.is_active():
max_merkle_path_length = self._settings.NEW_MAX_MERKLE_PATH_LENGTH

block.aux_pow.verify(block.get_base_hash(), max_merkle_path_length)
31 changes: 22 additions & 9 deletions hathor/verification/verification_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@

from typing_extensions import assert_never

from hathor.feature_activation.feature import Feature
from hathor.feature_activation.feature_service import BlockSignalingState, FeatureService
from hathor.feature_activation.model.feature_description import FeatureInfo
from hathor.profiler import get_cpu_profiler
from hathor.transaction import BaseTransaction, Block, MergeMinedBlock, Transaction, TxVersion
from hathor.transaction.token_creation_tx import TokenCreationTransaction
Expand All @@ -26,10 +29,11 @@


class VerificationService:
__slots__ = ('verifiers', )
__slots__ = ('verifiers', '_feature_service')

def __init__(self, *, verifiers: VertexVerifiers) -> None:
def __init__(self, *, verifiers: VertexVerifiers, feature_service: FeatureService | None = None) -> None:
self.verifiers = verifiers
self._feature_service = feature_service

def validate_basic(self, vertex: BaseTransaction, *, skip_block_weight_verification: bool = False) -> bool:
""" Run basic validations (all that are possible without dependencies) and update the validation state.
Expand Down Expand Up @@ -124,14 +128,18 @@ def verify(self, vertex: BaseTransaction, *, reject_locked_reward: bool = True)
"""Run all verifications. Raises on error.
Used by `self.validate_full`. Should not modify the validation state."""
assert self._feature_service is not None
# We assert with type() instead of isinstance() because each subclass has a specific branch.
match vertex.version:
case TxVersion.REGULAR_BLOCK:
assert type(vertex) is Block
self._verify_block(vertex)
signaling_state = self._feature_service.is_signaling_mandatory_features(vertex)
self._verify_block(vertex, signaling_state)
case TxVersion.MERGE_MINED_BLOCK:
assert type(vertex) is MergeMinedBlock
self._verify_merge_mined_block(vertex)
signaling_state = self._feature_service.is_signaling_mandatory_features(vertex)
feature_info = self._feature_service.get_feature_info(block=vertex)
self._verify_merge_mined_block(vertex, signaling_state, feature_info)
case TxVersion.REGULAR_TRANSACTION:
assert type(vertex) is Transaction
self._verify_tx(vertex, reject_locked_reward=reject_locked_reward)
Expand All @@ -142,7 +150,7 @@ def verify(self, vertex: BaseTransaction, *, reject_locked_reward: bool = True)
assert_never(vertex.version)

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

self.verifiers.block.verify_height(block)

self.verifiers.block.verify_mandatory_signaling(block)
self.verifiers.block.verify_mandatory_signaling(signaling_state)

def _verify_merge_mined_block(self, block: MergeMinedBlock) -> None:
self._verify_block(block)
def _verify_merge_mined_block(
self,
block: MergeMinedBlock,
signaling_state: BlockSignalingState,
feature_info: dict[Feature, FeatureInfo]
) -> None:
self.verifiers.merge_mined_block.verify_aux_pow(block, feature_info)
self._verify_block(block, signaling_state)

@cpu.profiler(key=lambda _, tx: 'tx-verify!{}'.format(tx.hash.hex()))
def _verify_tx(
Expand Down Expand Up @@ -237,7 +251,6 @@ def _verify_without_storage_block(self, block: Block) -> None:
self.verifiers.vertex.verify_sigops_output(block)

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

def _verify_without_storage_tx(self, tx: Transaction) -> None:
Expand Down
15 changes: 3 additions & 12 deletions hathor/verification/vertex_verifiers.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@

from hathor.conf.settings import HathorSettings
from hathor.daa import DifficultyAdjustmentAlgorithm
from hathor.feature_activation.feature_service import FeatureService
from hathor.verification.block_verifier import BlockVerifier
from hathor.verification.merge_mined_block_verifier import MergeMinedBlockVerifier
from hathor.verification.token_creation_transaction_verifier import TokenCreationTransactionVerifier
Expand All @@ -33,13 +32,7 @@ class VertexVerifiers(NamedTuple):
token_creation_tx: TokenCreationTransactionVerifier

@classmethod
def create_defaults(
cls,
*,
settings: HathorSettings,
daa: DifficultyAdjustmentAlgorithm,
feature_service: FeatureService,
) -> 'VertexVerifiers':
def create_defaults(cls, *, settings: HathorSettings, daa: DifficultyAdjustmentAlgorithm) -> 'VertexVerifiers':
"""
Create a VertexVerifiers instance using the default verifier for each vertex type,
from all required dependencies.
Expand All @@ -50,7 +43,6 @@ def create_defaults(
settings=settings,
vertex_verifier=vertex_verifier,
daa=daa,
feature_service=feature_service
)

@classmethod
Expand All @@ -60,13 +52,12 @@ def create(
settings: HathorSettings,
vertex_verifier: VertexVerifier,
daa: DifficultyAdjustmentAlgorithm,
feature_service: FeatureService,
) -> 'VertexVerifiers':
"""
Create a VertexVerifiers instance using a custom vertex_verifier.
"""
block_verifier = BlockVerifier(settings=settings, daa=daa, feature_service=feature_service)
merge_mined_block_verifier = MergeMinedBlockVerifier(settings=settings, feature_service=feature_service)
block_verifier = BlockVerifier(settings=settings, daa=daa)
merge_mined_block_verifier = MergeMinedBlockVerifier(settings=settings)
tx_verifier = TransactionVerifier(settings=settings, daa=daa)
token_creation_tx_verifier = TokenCreationTransactionVerifier(settings=settings)

Expand Down
2 changes: 1 addition & 1 deletion hathor/vertex_handler/vertex_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ def _log_feature_states(self, vertex: BaseTransaction) -> None:
if not isinstance(vertex, Block):
return

feature_descriptions = self._feature_service.get_bits_description(block=vertex)
feature_descriptions = self._feature_service.get_feature_info(block=vertex)
state_by_feature = {
feature.value: description.state.value
for feature, description in feature_descriptions.items()
Expand Down
Loading
Loading