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
+ 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 :
37
63
"""Returns the state of a feature at a certain block. Uses block metadata to cache states."""
38
64
39
65
# 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:
54
80
previous_boundary_block = self ._get_ancestor_at_height (block = block , height = previous_boundary_height )
55
81
previous_boundary_state = self .get_state (block = previous_boundary_block , feature = feature )
56
82
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
+
57
87
if offset_to_boundary != 0 :
58
88
return previous_boundary_state
59
89
@@ -63,14 +93,14 @@ 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
+ block .set_feature_state (feature = feature , state = new_state )
67
97
68
98
return new_state
69
99
70
100
def _calculate_new_state (
71
101
self ,
72
102
* ,
73
- boundary_block : Block ,
103
+ boundary_block : ' Block' ,
74
104
feature : Feature ,
75
105
previous_state : FeatureState
76
106
) -> FeatureState :
@@ -136,7 +166,7 @@ def _calculate_new_state(
136
166
137
167
raise ValueError (f'Unknown previous state: { previous_state } ' )
138
168
139
- def get_bits_description (self , * , block : Block ) -> dict [Feature , FeatureDescription ]:
169
+ def get_bits_description (self , * , block : ' Block' ) -> dict [Feature , FeatureDescription ]:
140
170
"""Returns the criteria definition and feature state for all features at a certain block."""
141
171
return {
142
172
feature : FeatureDescription (
@@ -146,7 +176,7 @@ def get_bits_description(self, *, block: Block) -> dict[Feature, FeatureDescript
146
176
for feature , criteria in self ._feature_settings .features .items ()
147
177
}
148
178
149
- def _get_ancestor_at_height (self , * , block : Block , height : int ) -> Block :
179
+ def _get_ancestor_at_height (self , * , block : ' Block' , height : int ) -> ' Block' :
150
180
"""
151
181
Given a block, returns its ancestor at a specific height.
152
182
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:
158
188
metadata = block .get_metadata ()
159
189
160
190
if not metadata .voided_by and (ancestor := self ._tx_storage .get_transaction_by_height (height )):
191
+ from hathor .transaction import Block
161
192
assert isinstance (ancestor , Block )
162
193
return ancestor
163
194
164
195
return _get_ancestor_iteratively (block = block , ancestor_height = height )
165
196
166
197
167
- def _get_ancestor_iteratively (* , block : Block , ancestor_height : int ) -> Block :
198
+ def _get_ancestor_iteratively (* , block : ' Block' , ancestor_height : int ) -> ' Block' :
168
199
"""Given a block, returns its ancestor at a specific height by iterating over its ancestors. This is slow."""
169
200
# TODO: there are further optimizations to be done here, the latest common block height could be persisted in
170
201
# metadata, so we could still use the height index if the requested height is before that height.
0 commit comments