Skip to content

Commit

Permalink
refactor(feature-activation): improve usage check and signal verifica…
Browse files Browse the repository at this point in the history
…tion
  • Loading branch information
glevco committed Dec 22, 2023
1 parent 7eab19b commit cbc3dc1
Show file tree
Hide file tree
Showing 12 changed files with 87 additions and 85 deletions.
14 changes: 5 additions & 9 deletions hathor/builder/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ class BuildArtifacts(NamedTuple):


_VertexVerifiersBuilder: TypeAlias = Callable[
[HathorSettingsType, DifficultyAdjustmentAlgorithm, FeatureService],
[HathorSettingsType, DifficultyAdjustmentAlgorithm],
VertexVerifiers
]

Expand Down Expand Up @@ -473,24 +473,20 @@ 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

def _get_or_create_vertex_verifiers(self) -> VertexVerifiers:
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 @@ -222,12 +222,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
6 changes: 6 additions & 0 deletions hathor/feature_activation/bit_signaling_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ def start(self) -> None:
Log information related to bit signaling. Must be called after the storage is ready and migrations have
been applied.
"""
if not self._feature_service.is_usage_enabled():
return

best_block = self._tx_storage.get_best_block()

self._warn_non_signaling_features(best_block)
Expand All @@ -74,6 +77,9 @@ def generate_signal_bits(self, *, block: Block, log: bool = False) -> int:
Returns: a number that represents the signal bits in binary.
"""
if not self._feature_service.is_usage_enabled():
return 0

signaling_features = self._get_signaling_features(block)
signal_bits = 0

Expand Down
18 changes: 16 additions & 2 deletions hathor/feature_activation/feature_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,14 @@
from hathor.transaction.storage import TransactionStorage


@dataclass(frozen=True, slots=True)
class FeatureActivationIsDisabled:
"""Represent that Feature Activation is disabled and therefore the block signaling state cannot be calculated."""


@dataclass(frozen=True, slots=True)
class BlockIsSignaling:
"""Represent that a block is correctly signaling support for all currently mandatory features."""
pass


@dataclass(frozen=True, slots=True)
Expand All @@ -37,7 +41,7 @@ class BlockIsMissingSignal:
feature: Feature


BlockSignalingState: TypeAlias = BlockIsSignaling | BlockIsMissingSignal
BlockSignalingState: TypeAlias = FeatureActivationIsDisabled | BlockIsSignaling | BlockIsMissingSignal


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

def is_usage_enabled(self) -> bool:
"""Return whether Feature Activation usage is enabled."""
return self._feature_settings.enable_usage

def is_feature_active(self, *, block: 'Block', feature: Feature) -> bool:
"""Returns whether a Feature is active at a certain block."""
assert self.is_usage_enabled()
state = self.get_state(block=block, feature=feature)

return state == FeatureState.ACTIVE
Expand All @@ -58,6 +67,9 @@ def is_signaling_mandatory_features(self, block: 'Block') -> BlockSignalingState
Return whether a block is signaling features that are mandatory, that is, any feature currently in the
MUST_SIGNAL phase.
"""
if not self.is_usage_enabled():
return FeatureActivationIsDisabled()

bit_counts = block.get_feature_activation_bit_counts()
height = block.get_height()
offset_to_boundary = height % self._feature_settings.evaluation_interval
Expand All @@ -82,6 +94,7 @@ def is_signaling_mandatory_features(self, block: 'Block') -> BlockSignalingState

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

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

def get_bits_description(self, *, block: 'Block') -> dict[Feature, FeatureDescription]:
"""Returns the criteria definition and feature state for all features at a certain block."""
assert self.is_usage_enabled()
return {
feature: FeatureDescription(
criteria=criteria,
Expand Down
2 changes: 1 addition & 1 deletion hathor/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -1061,7 +1061,7 @@ def tx_fully_validated(self, tx: BaseTransaction, *, quiet: bool) -> None:

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

feature_descriptions = self._feature_service.get_bits_description(block=vertex)
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_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,
)
27 changes: 12 additions & 15 deletions hathor/verification/block_verifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,16 @@
# 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,
FeatureActivationIsDisabled,
)
from hathor.transaction import Block
from hathor.transaction.exceptions import (
BlockMustSignalError,
Expand All @@ -28,18 +35,16 @@


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

def __init__(
self,
*,
settings: HathorSettings,
daa: DifficultyAdjustmentAlgorithm,
feature_service: FeatureService | None = None
) -> 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 @@ -80,22 +85,14 @@ 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."""
if not self._settings.FEATURE_ACTIVATION.enable_usage:
return

assert self._feature_service is not None

signaling_state = self._feature_service.is_signaling_mandatory_features(block)

match signaling_state:
case BlockIsSignaling():
case FeatureActivationIsDisabled() | BlockIsSignaling():
return
case BlockIsMissingSignal(feature):
raise BlockMustSignalError(
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)
21 changes: 13 additions & 8 deletions hathor/verification/verification_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

from typing_extensions import assert_never

from hathor.feature_activation.feature_service import BlockSignalingState, FeatureService
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 +27,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 +126,17 @@ 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)
self._verify_merge_mined_block(vertex, signaling_state)
case TxVersion.REGULAR_TRANSACTION:
assert type(vertex) is Transaction
self._verify_tx(vertex, reject_locked_reward=reject_locked_reward)
Expand All @@ -142,7 +147,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 +168,10 @@ 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) -> None:
self._verify_block(block, signaling_state)

@cpu.profiler(key=lambda _, tx: 'tx-verify!{}'.format(tx.hash.hex()))
def _verify_tx(
Expand Down
13 changes: 2 additions & 11 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 | None = None,
) -> '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,12 +52,11 @@ def create(
settings: HathorSettings,
vertex_verifier: VertexVerifier,
daa: DifficultyAdjustmentAlgorithm,
feature_service: FeatureService | None = None,
) -> 'VertexVerifiers':
"""
Create a VertexVerifiers instance using a custom vertex_verifier.
"""
block_verifier = BlockVerifier(settings=settings, daa=daa, feature_service=feature_service)
block_verifier = BlockVerifier(settings=settings, daa=daa)
merge_mined_block_verifier = MergeMinedBlockVerifier()
tx_verifier = TransactionVerifier(settings=settings, daa=daa)
token_creation_tx_verifier = TokenCreationTransactionVerifier(settings=settings)
Expand Down
Loading

0 comments on commit cbc3dc1

Please sign in to comment.