Skip to content

Commit 706bb68

Browse files
committed
feat(feature-activation): implement must signal
1 parent a5b0b94 commit 706bb68

File tree

16 files changed

+332
-233
lines changed

16 files changed

+332
-233
lines changed

hathor/builder/builder.py

+22-14
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ class BuildArtifacts(NamedTuple):
6363
pubsub: PubSubManager
6464
consensus: ConsensusAlgorithm
6565
tx_storage: TransactionStorage
66+
feature_service: FeatureService
6667
indexes: Optional[IndexesManager]
6768
wallet: Optional[BaseWallet]
6869
rocksdb_storage: Optional[RocksDBStorage]
@@ -144,7 +145,7 @@ def build(self) -> BuildArtifacts:
144145
if self._network is None:
145146
raise TypeError('you must set a network')
146147

147-
settings = self._get_or_create_settings()
148+
settings = self.get_or_create_settings()
148149
reactor = self._get_reactor()
149150
pubsub = self._get_or_create_pubsub()
150151

@@ -158,9 +159,9 @@ def build(self) -> BuildArtifacts:
158159
wallet = self._get_or_create_wallet()
159160
event_manager = self._get_or_create_event_manager()
160161
indexes = self._get_or_create_indexes_manager()
161-
tx_storage = self._get_or_create_tx_storage(indexes)
162-
feature_service = self._get_or_create_feature_service(tx_storage)
163-
bit_signaling_service = self._get_or_create_bit_signaling_service(tx_storage)
162+
tx_storage = self._get_or_create_tx_storage()
163+
feature_service = self.get_or_create_feature_service()
164+
bit_signaling_service = self._get_or_create_bit_signaling_service()
164165
verification_service = self._get_or_create_verification_service()
165166

166167
if self._enable_address_index:
@@ -221,6 +222,7 @@ def build(self) -> BuildArtifacts:
221222
wallet=wallet,
222223
rocksdb_storage=self._rocksdb_storage,
223224
stratum_factory=stratum_factory,
225+
feature_service=feature_service,
224226
)
225227

226228
return self.artifacts
@@ -264,7 +266,7 @@ def set_peer_id(self, peer_id: PeerId) -> 'Builder':
264266
self._peer_id = peer_id
265267
return self
266268

267-
def _get_or_create_settings(self) -> HathorSettingsType:
269+
def get_or_create_settings(self) -> HathorSettingsType:
268270
if self._settings is None:
269271
self._settings = get_settings()
270272
return self._settings
@@ -278,7 +280,7 @@ def _get_soft_voided_tx_ids(self) -> set[bytes]:
278280
if self._soft_voided_tx_ids is not None:
279281
return self._soft_voided_tx_ids
280282

281-
settings = self._get_or_create_settings()
283+
settings = self.get_or_create_settings()
282284

283285
return set(settings.SOFT_VOIDED_TX_IDS)
284286

@@ -352,7 +354,9 @@ def _get_or_create_indexes_manager(self) -> IndexesManager:
352354

353355
return self._indexes_manager
354356

355-
def _get_or_create_tx_storage(self, indexes: IndexesManager) -> TransactionStorage:
357+
def _get_or_create_tx_storage(self) -> TransactionStorage:
358+
indexes = self._get_or_create_indexes_manager()
359+
356360
if self._tx_storage is not None:
357361
# If a tx storage is provided, set the indexes manager to it.
358362
self._tx_storage.indexes = indexes
@@ -408,22 +412,25 @@ def _get_or_create_event_manager(self) -> EventManager:
408412

409413
return self._event_manager
410414

411-
def _get_or_create_feature_service(self, tx_storage: TransactionStorage) -> FeatureService:
415+
def get_or_create_feature_service(self) -> FeatureService:
412416
if self._feature_service is None:
413-
settings = self._get_or_create_settings()
417+
settings = self.get_or_create_settings()
418+
tx_storage = self._get_or_create_tx_storage()
414419
self._feature_service = FeatureService(
415420
feature_settings=settings.FEATURE_ACTIVATION,
416421
tx_storage=tx_storage
417422
)
418423

419424
return self._feature_service
420425

421-
def _get_or_create_bit_signaling_service(self, tx_storage: TransactionStorage) -> BitSignalingService:
426+
def _get_or_create_bit_signaling_service(self) -> BitSignalingService:
422427
if self._bit_signaling_service is None:
423-
settings = self._get_or_create_settings()
428+
settings = self.get_or_create_settings()
429+
tx_storage = self._get_or_create_tx_storage()
430+
feature_service = self.get_or_create_feature_service()
424431
self._bit_signaling_service = BitSignalingService(
425432
feature_settings=settings.FEATURE_ACTIVATION,
426-
feature_service=self._get_or_create_feature_service(tx_storage),
433+
feature_service=feature_service,
427434
tx_storage=tx_storage,
428435
support_features=self._support_features,
429436
not_support_features=self._not_support_features,
@@ -440,8 +447,9 @@ def _get_or_create_verification_service(self) -> VerificationService:
440447

441448
def _get_or_create_vertex_verifiers(self) -> VertexVerifiers:
442449
if self._vertex_verifiers is None:
443-
settings = self._get_or_create_settings()
444-
self._vertex_verifiers = VertexVerifiers.create(settings=settings)
450+
settings = self.get_or_create_settings()
451+
feature_service = self.get_or_create_feature_service()
452+
self._vertex_verifiers = VertexVerifiers.create(settings=settings, feature_service=feature_service)
445453

446454
return self._vertex_verifiers
447455

hathor/builder/cli_builder.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ def create_manager(self, reactor: Reactor) -> HathorManager:
203203
not_support_features=self._args.signal_not_support
204204
)
205205

206-
vertex_verifiers = VertexVerifiers.create(settings=settings)
206+
vertex_verifiers = VertexVerifiers.create(settings=settings, feature_service=self.feature_service)
207207
verification_service = VerificationService(verifiers=vertex_verifiers)
208208

209209
p2p_manager = ConnectionsManager(

hathor/cli/run_node.py

+1
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,7 @@ def prepare(self, *, register_resources: bool = True) -> None:
191191
wallet=self.manager.wallet,
192192
rocksdb_storage=getattr(builder, 'rocksdb_storage', None),
193193
stratum_factory=self.manager.stratum_factory,
194+
feature_service=self.manager._feature_service
194195
)
195196

196197
def start_sentry_if_possible(self) -> None:

hathor/feature_activation/feature_service.py

+41-10
Original file line numberDiff line numberDiff line change
@@ -12,28 +12,54 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
from typing import TYPE_CHECKING
16+
1517
from hathor.feature_activation.feature import Feature
1618
from hathor.feature_activation.model.feature_description import FeatureDescription
1719
from hathor.feature_activation.model.feature_state import FeatureState
1820
from hathor.feature_activation.settings import Settings as FeatureSettings
19-
from hathor.transaction import Block
20-
from hathor.transaction.storage import TransactionStorage
21+
22+
if TYPE_CHECKING:
23+
from hathor.transaction import Block
24+
from hathor.transaction.storage import TransactionStorage
2125

2226

2327
class FeatureService:
2428
__slots__ = ('_feature_settings', '_tx_storage')
2529

26-
def __init__(self, *, feature_settings: FeatureSettings, tx_storage: TransactionStorage) -> None:
30+
def __init__(self, *, feature_settings: FeatureSettings, tx_storage: 'TransactionStorage') -> None:
2731
self._feature_settings = feature_settings
2832
self._tx_storage = tx_storage
2933

30-
def is_feature_active(self, *, block: Block, feature: Feature) -> bool:
34+
def is_feature_active(self, *, block: 'Block', feature: Feature) -> bool:
3135
"""Returns whether a Feature is active at a certain block."""
3236
state = self.get_state(block=block, feature=feature)
3337

3438
return state == FeatureState.ACTIVE
3539

36-
def get_state(self, *, block: Block, feature: Feature) -> FeatureState:
40+
def check_must_signal(self, block: 'Block') -> bool:
41+
descriptions = self.get_bits_description(block=block)
42+
43+
for feature, description in descriptions.items():
44+
criteria = self._feature_settings.features[feature]
45+
46+
if description.state is FeatureState.MUST_SIGNAL:
47+
# TODO: make functions
48+
counts = block.get_feature_activation_bit_counts()
49+
count = counts[criteria.bit]
50+
threshold = criteria.get_threshold(self._feature_settings)
51+
missing_signals = threshold - count
52+
height = block.get_height()
53+
# TODO: Check edges
54+
offset_to_boundary = height % self._feature_settings.evaluation_interval
55+
remaining_blocks = self._feature_settings.evaluation_interval - offset_to_boundary - 1
56+
57+
if missing_signals > remaining_blocks:
58+
return True
59+
60+
return False
61+
62+
def get_state(self, *, block: 'Block', feature: Feature) -> FeatureState:
3763
"""Returns the state of a feature at a certain block. Uses block metadata to cache states."""
3864

3965
# per definition, the genesis block is in the DEFINED state for all features
@@ -54,6 +80,10 @@ def get_state(self, *, block: Block, feature: Feature) -> FeatureState:
5480
previous_boundary_block = self._get_ancestor_at_height(block=block, height=previous_boundary_height)
5581
previous_boundary_state = self.get_state(block=previous_boundary_block, feature=feature)
5682

83+
# Instead of caching the current block we cache the previous boundary block, as the current block may not
84+
# be verified yet, so we cannot persist its metadata.
85+
previous_boundary_block.set_feature_state(feature=feature, state=previous_boundary_state, save=True)
86+
5787
if offset_to_boundary != 0:
5888
return previous_boundary_state
5989

@@ -63,14 +93,14 @@ def get_state(self, *, block: Block, feature: Feature) -> FeatureState:
6393
previous_state=previous_boundary_state
6494
)
6595

66-
block.update_feature_state(feature=feature, state=new_state)
96+
block.set_feature_state(feature=feature, state=new_state)
6797

6898
return new_state
6999

70100
def _calculate_new_state(
71101
self,
72102
*,
73-
boundary_block: Block,
103+
boundary_block: 'Block',
74104
feature: Feature,
75105
previous_state: FeatureState
76106
) -> FeatureState:
@@ -136,7 +166,7 @@ def _calculate_new_state(
136166

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

139-
def get_bits_description(self, *, block: Block) -> dict[Feature, FeatureDescription]:
169+
def get_bits_description(self, *, block: 'Block') -> dict[Feature, FeatureDescription]:
140170
"""Returns the criteria definition and feature state for all features at a certain block."""
141171
return {
142172
feature: FeatureDescription(
@@ -146,7 +176,7 @@ def get_bits_description(self, *, block: Block) -> dict[Feature, FeatureDescript
146176
for feature, criteria in self._feature_settings.features.items()
147177
}
148178

149-
def _get_ancestor_at_height(self, *, block: Block, height: int) -> Block:
179+
def _get_ancestor_at_height(self, *, block: 'Block', height: int) -> 'Block':
150180
"""
151181
Given a block, returns its ancestor at a specific height.
152182
Uses the height index if the block is in the best blockchain, or search iteratively otherwise.
@@ -158,13 +188,14 @@ def _get_ancestor_at_height(self, *, block: Block, height: int) -> Block:
158188
metadata = block.get_metadata()
159189

160190
if not metadata.voided_by and (ancestor := self._tx_storage.get_transaction_by_height(height)):
191+
from hathor.transaction import Block
161192
assert isinstance(ancestor, Block)
162193
return ancestor
163194

164195
return _get_ancestor_iteratively(block=block, ancestor_height=height)
165196

166197

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

hathor/simulator/simulator.py

+15-9
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
from structlog import get_logger
2222

2323
from hathor.builder import BuildArtifacts, Builder
24-
from hathor.conf.get_settings import get_settings
2524
from hathor.daa import TestMode, _set_test_mode
2625
from hathor.manager import HathorManager
2726
from hathor.p2p.peer_id import PeerId
@@ -114,7 +113,6 @@ def __init__(self, seed: Optional[int] = None):
114113
seed = secrets.randbits(64)
115114
self.seed = seed
116115
self.rng = Random(self.seed)
117-
self.settings = get_settings()
118116
self._network = 'testnet'
119117
self._clock = MemoryReactorHeapClock()
120118
self._peers: OrderedDict[str, HathorManager] = OrderedDict()
@@ -169,17 +167,25 @@ def create_artifacts(self, builder: Optional[Builder] = None) -> BuildArtifacts:
169167
wallet = HDWallet(gap_limit=2)
170168
wallet._manually_initialize()
171169

170+
builder = builder \
171+
.set_reactor(self._clock) \
172+
.set_rng(Random(self.rng.getrandbits(64))) \
173+
.set_wallet(wallet)
174+
175+
feature_service = builder.get_or_create_feature_service()
176+
settings = builder.get_or_create_settings()
177+
172178
vertex_verifiers = VertexVerifiers(
173-
block=SimulatorBlockVerifier(settings=self.settings),
174-
merge_mined_block=SimulatorMergeMinedBlockVerifier(settings=self.settings),
175-
tx=SimulatorTransactionVerifier(settings=self.settings),
176-
token_creation_tx=SimulatorTokenCreationTransactionVerifier(settings=self.settings),
179+
block=SimulatorBlockVerifier(settings=settings, feature_service=feature_service),
180+
merge_mined_block=SimulatorMergeMinedBlockVerifier(
181+
settings=settings,
182+
feature_service=feature_service
183+
),
184+
tx=SimulatorTransactionVerifier(settings=settings),
185+
token_creation_tx=SimulatorTokenCreationTransactionVerifier(settings=settings),
177186
)
178187

179188
artifacts = builder \
180-
.set_reactor(self._clock) \
181-
.set_rng(Random(self.rng.getrandbits(64))) \
182-
.set_wallet(wallet) \
183189
.set_vertex_verifiers(vertex_verifiers) \
184190
.build()
185191

hathor/transaction/base_transaction.py

-18
Original file line numberDiff line numberDiff line change
@@ -672,20 +672,13 @@ def get_metadata(self, *, force_reload: bool = False, use_storage: bool = True)
672672
# happens include generating new mining blocks and some tests
673673
height = self.calculate_height() if self.storage else None
674674
score = self.weight if self.is_genesis else 0
675-
kwargs: dict[str, Any] = {}
676-
677-
if self.is_block:
678-
from hathor.transaction import Block
679-
assert isinstance(self, Block)
680-
kwargs['feature_activation_bit_counts'] = self.calculate_feature_activation_bit_counts()
681675

682676
metadata = TransactionMetadata(
683677
hash=self.hash,
684678
accumulated_weight=self.weight,
685679
height=height,
686680
score=score,
687681
min_height=0,
688-
**kwargs
689682
)
690683
self._metadata = metadata
691684
if not metadata.hash:
@@ -769,7 +762,6 @@ def update_initial_metadata(self, *, save: bool = True) -> None:
769762
self._update_height_metadata()
770763
self._update_parents_children_metadata()
771764
self._update_reward_lock_metadata()
772-
self._update_feature_activation_bit_counts_metadata()
773765
if save:
774766
assert self.storage is not None
775767
self.storage.save_transaction(self, only_metadata=True)
@@ -795,16 +787,6 @@ def _update_parents_children_metadata(self) -> None:
795787
metadata.children.append(self.hash)
796788
self.storage.save_transaction(parent, only_metadata=True)
797789

798-
def _update_feature_activation_bit_counts_metadata(self) -> None:
799-
"""Update the block feature_activation_bit_counts metadata."""
800-
if not self.is_block:
801-
return
802-
803-
from hathor.transaction import Block
804-
assert isinstance(self, Block)
805-
metadata = self.get_metadata()
806-
metadata.feature_activation_bit_counts = self.calculate_feature_activation_bit_counts()
807-
808790
def update_timestamp(self, now: int) -> None:
809791
"""Update this tx's timestamp
810792

0 commit comments

Comments
 (0)