12
12
# See the License for the specific language governing permissions and
13
13
# limitations under the License.
14
14
15
+ from typing import TYPE_CHECKING
16
+
15
17
from hathor .feature_activation .feature import Feature
16
18
from hathor .feature_activation .model .feature_description import FeatureDescription
17
19
from hathor .feature_activation .model .feature_state import FeatureState
18
20
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
21
25
22
26
23
27
class FeatureService :
24
28
__slots__ = ('_feature_settings' , '_tx_storage' )
25
29
26
- def __init__ (self , * , feature_settings : FeatureSettings , tx_storage : TransactionStorage ) -> None :
30
+ def __init__ (self , * , feature_settings : FeatureSettings , tx_storage : ' TransactionStorage' ) -> None :
27
31
self ._feature_settings = feature_settings
28
32
self ._tx_storage = tx_storage
29
33
30
- def is_feature_active (self , * , block : Block , feature : Feature ) -> bool :
34
+ def is_feature_active (self , * , block : ' Block' , feature : Feature ) -> bool :
31
35
"""Returns whether a Feature is active at a certain block."""
32
36
state = self .get_state (block = block , feature = feature )
33
37
34
38
return state == FeatureState .ACTIVE
35
39
36
- def get_state (self , * , block : Block , feature : Feature ) -> FeatureState :
40
+ def check_must_signal (self , block : 'Block' ) -> bool :
41
+ """Return whether a block is missing mandatory signaling for any feature currently in the MUST_SIGNAL phase."""
42
+ descriptions = self .get_bits_description (block = block )
43
+
44
+ for feature , description in descriptions .items ():
45
+ criteria = self ._feature_settings .features [feature ]
46
+
47
+ if description .state is FeatureState .MUST_SIGNAL :
48
+ # TODO: make functions
49
+ counts = block .get_feature_activation_bit_counts ()
50
+ count = counts [criteria .bit ]
51
+ threshold = criteria .get_threshold (self ._feature_settings )
52
+ missing_signals = threshold - count
53
+ height = block .get_height ()
54
+ # TODO: Check edges
55
+ offset_to_boundary = height % self ._feature_settings .evaluation_interval
56
+ remaining_blocks = self ._feature_settings .evaluation_interval - offset_to_boundary - 1
57
+
58
+ if missing_signals > remaining_blocks :
59
+ return True
60
+
61
+ return False
62
+
63
+ def get_state (self , * , block : 'Block' , feature : Feature ) -> FeatureState :
37
64
"""Returns the state of a feature at a certain block. Uses block metadata to cache states."""
38
65
39
66
# per definition, the genesis block is in the DEFINED state for all features
@@ -54,6 +81,9 @@ def get_state(self, *, block: Block, feature: Feature) -> FeatureState:
54
81
previous_boundary_block = self ._get_ancestor_at_height (block = block , height = previous_boundary_height )
55
82
previous_boundary_state = self .get_state (block = previous_boundary_block , feature = feature )
56
83
84
+ # We cache _and save_ the state of the previous boundary block that we just got.
85
+ previous_boundary_block .set_feature_state (feature = feature , state = previous_boundary_state , save = True )
86
+
57
87
if offset_to_boundary != 0 :
58
88
return previous_boundary_state
59
89
@@ -63,14 +93,16 @@ def get_state(self, *, block: Block, feature: Feature) -> FeatureState:
63
93
previous_state = previous_boundary_state
64
94
)
65
95
66
- block .update_feature_state (feature = feature , state = new_state )
96
+ # We cache the just calculated state of the current block _without saving it_, as it may still be unverified,
97
+ # so we cannot persist its metadata. That's why we cache and save the previous boundary block above.
98
+ block .set_feature_state (feature = feature , state = new_state )
67
99
68
100
return new_state
69
101
70
102
def _calculate_new_state (
71
103
self ,
72
104
* ,
73
- boundary_block : Block ,
105
+ boundary_block : ' Block' ,
74
106
feature : Feature ,
75
107
previous_state : FeatureState
76
108
) -> FeatureState :
@@ -136,7 +168,7 @@ def _calculate_new_state(
136
168
137
169
raise ValueError (f'Unknown previous state: { previous_state } ' )
138
170
139
- def get_bits_description (self , * , block : Block ) -> dict [Feature , FeatureDescription ]:
171
+ def get_bits_description (self , * , block : ' Block' ) -> dict [Feature , FeatureDescription ]:
140
172
"""Returns the criteria definition and feature state for all features at a certain block."""
141
173
return {
142
174
feature : FeatureDescription (
@@ -146,7 +178,7 @@ def get_bits_description(self, *, block: Block) -> dict[Feature, FeatureDescript
146
178
for feature , criteria in self ._feature_settings .features .items ()
147
179
}
148
180
149
- def _get_ancestor_at_height (self , * , block : Block , height : int ) -> Block :
181
+ def _get_ancestor_at_height (self , * , block : ' Block' , height : int ) -> ' Block' :
150
182
"""
151
183
Given a block, returns its ancestor at a specific height.
152
184
Uses the height index if the block is in the best blockchain, or search iteratively otherwise.
@@ -158,13 +190,14 @@ def _get_ancestor_at_height(self, *, block: Block, height: int) -> Block:
158
190
metadata = block .get_metadata ()
159
191
160
192
if not metadata .voided_by and (ancestor := self ._tx_storage .get_transaction_by_height (height )):
193
+ from hathor .transaction import Block
161
194
assert isinstance (ancestor , Block )
162
195
return ancestor
163
196
164
197
return _get_ancestor_iteratively (block = block , ancestor_height = height )
165
198
166
199
167
- def _get_ancestor_iteratively (* , block : Block , ancestor_height : int ) -> Block :
200
+ def _get_ancestor_iteratively (* , block : ' Block' , ancestor_height : int ) -> ' Block' :
168
201
"""Given a block, returns its ancestor at a specific height by iterating over its ancestors. This is slow."""
169
202
# TODO: there are further optimizations to be done here, the latest common block height could be persisted in
170
203
# metadata, so we could still use the height index if the requested height is before that height.
0 commit comments