Skip to content

Commit

Permalink
feat(feature-activation): implement block ancestor optimization
Browse files Browse the repository at this point in the history
  • Loading branch information
glevco committed Jun 15, 2023
1 parent 441c255 commit 827f003
Show file tree
Hide file tree
Showing 8 changed files with 380 additions and 57 deletions.
9 changes: 6 additions & 3 deletions hathor/builder/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ def build(self) -> BuildArtifacts:
if self._enable_stratum_server:
stratum_factory = self._create_stratum_server(manager)

feature_service = self._create_feature_service()
feature_service = self._create_feature_service(tx_storage)

self.artifacts = BuildArtifacts(
peer_id=peer_id,
Expand Down Expand Up @@ -272,8 +272,11 @@ def _create_stratum_server(self, manager: HathorManager) -> StratumFactory:
manager.metrics.stratum_factory = stratum_factory
return stratum_factory

def _create_feature_service(self) -> FeatureService:
return FeatureService(feature_settings=self._settings.FEATURE_ACTIVATION)
def _create_feature_service(self, tx_storage: TransactionStorage) -> FeatureService:
return FeatureService(
feature_settings=self._settings.FEATURE_ACTIVATION,
tx_storage=tx_storage
)

def _get_or_create_rocksdb_storage(self) -> RocksDBStorage:
assert self._rocksdb_path is not None
Expand Down
5 changes: 4 additions & 1 deletion hathor/cli/run_node.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,10 @@ def prepare(self, *, register_resources: bool = True) -> None:
from hathor.feature_activation.feature_service import FeatureService
settings = HathorSettings()

feature_service = FeatureService(feature_settings=settings.FEATURE_ACTIVATION)
feature_service = FeatureService(
feature_settings=settings.FEATURE_ACTIVATION,
tx_storage=self.manager.tx_storage
)

if register_resources:
resources_builder = ResourcesBuilder(self.manager, self._args, builder.event_ws_factory, feature_service)
Expand Down
37 changes: 26 additions & 11 deletions hathor/feature_activation/feature_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,15 @@
from hathor.feature_activation.model.feature_state import FeatureState
from hathor.feature_activation.settings import Settings as FeatureSettings
from hathor.transaction import Block
from hathor.transaction.storage import TransactionStorage


class FeatureService:
__slots__ = ('_feature_settings',)
__slots__ = ('_feature_settings', '_tx_storage')

def __init__(self, *, feature_settings: FeatureSettings) -> None:
def __init__(self, *, feature_settings: FeatureSettings, tx_storage: TransactionStorage) -> None:
self._feature_settings = feature_settings
self._tx_storage = tx_storage

def is_feature_active(self, *, block: Block, feature: Feature) -> bool:
"""Returns whether a Feature is active at a certain block."""
Expand All @@ -45,7 +47,7 @@ def get_state(self, *, block: Block, feature: Feature) -> FeatureState:
offset_to_boundary = height % self._feature_settings.evaluation_interval
offset_to_previous_boundary = offset_to_boundary or self._feature_settings.evaluation_interval
previous_boundary_height = height - offset_to_previous_boundary
previous_boundary_block = _get_ancestor_at_height(block=block, height=previous_boundary_height)
previous_boundary_block = self._get_ancestor_at_height(block=block, height=previous_boundary_height)
previous_state = self.get_state(block=previous_boundary_block, feature=feature)

if offset_to_boundary != 0:
Expand Down Expand Up @@ -133,17 +135,30 @@ def get_bits_description(self, *, block: Block) -> dict[Feature, FeatureDescript
for feature, criteria in self._feature_settings.features.items()
}

def _get_ancestor_at_height(self, *, block: Block, height: int) -> Block:
"""
Given a block, returns its ancestor at a specific height.
Uses the height index if the block is in the best blockchain, or search iteratively otherwise.
"""
assert height < block.get_height(), (
f"ancestor height must be lower than the block's height: {height} >= {block.get_height()}"
)

metadata = block.get_metadata()

if not metadata.voided_by and (ancestor := self._tx_storage.get_transaction_by_height(height)):
assert isinstance(ancestor, Block)
return ancestor

return _get_ancestor_iteratively(block=block, ancestor_height=height)

def _get_ancestor_at_height(*, block: Block, height: int) -> Block:
"""Given a block, returns its ancestor at a specific height."""
# TODO: there may be more optimized ways of doing this using the height index,
# but what if we're not in the best blockchain?
assert height < block.get_height(), (
f"ancestor height must be lower than the block's height: {height} >= {block.get_height()}"
)

def _get_ancestor_iteratively(*, block: Block, ancestor_height: int) -> Block:
"""Given a block, returns its ancestor at a specific height by iterating over its ancestors. This is slow."""
# TODO: there are further optimizations to be done here, the latest common block height could be persisted in
# metadata, so we could still use the height index if the requested height is before that height.
ancestor = block
while ancestor.get_height() > height:
while ancestor.get_height() > ancestor_height:
ancestor = ancestor.get_block_parent()

return ancestor
7 changes: 5 additions & 2 deletions hathor/simulator/simulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
from hathor.conf import HathorSettings
from hathor.daa import TestMode, _set_test_mode
from hathor.manager import HathorManager
from hathor.p2p.peer_id import PeerId
from hathor.simulator.clock import HeapClock
from hathor.simulator.miner.geometric_miner import GeometricMiner
from hathor.simulator.tx_generator import RandomTransactionGenerator
Expand Down Expand Up @@ -144,26 +145,28 @@ def get_default_builder(self) -> Builder:
"""
return Builder() \
.set_network(self._network) \
.set_peer_id(PeerId()) \
.set_soft_voided_tx_ids(set()) \
.enable_full_verification() \
.enable_sync_v1() \
.enable_sync_v2() \
.use_memory()

def create_peer(self, builder: Builder) -> HathorManager:
def create_peer(self, builder: Optional[Builder] = None) -> HathorManager:
"""
Returns a manager from a builder, after configuring it for simulator use.
You may get a builder from get_default_builder() for convenience.
"""
artifacts = self.create_artifacts(builder)
return artifacts.manager

def create_artifacts(self, builder: Builder) -> BuildArtifacts:
def create_artifacts(self, builder: Optional[Builder] = None) -> BuildArtifacts:
"""
Returns build artifacts from a builder, after configuring it for simulator use.
You may get a builder from get_default_builder() for convenience.
"""
assert self._started, 'Simulator is not started.'
builder = builder or self.get_default_builder()

wallet = HDWallet(gap_limit=2)
wallet._manually_initialize()
Expand Down
7 changes: 7 additions & 0 deletions hathor/transaction/storage/transaction_storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -561,6 +561,13 @@ def get_transaction(self, hash_bytes: bytes) -> BaseTransaction:
self.post_get_validation(tx)
return tx

def get_transaction_by_height(self, height: int) -> Optional[BaseTransaction]:
"""Returns a transaction from the height index. This is fast."""
assert self.indexes is not None
ancestor_hash = self.indexes.height.get(height)

return None if ancestor_hash is None else self.get_transaction(ancestor_hash)

def get_metadata(self, hash_bytes: bytes) -> Optional[TransactionMetadata]:
"""Returns the transaction metadata with hash `hash_bytes`.
Expand Down
Loading

0 comments on commit 827f003

Please sign in to comment.