17
17
from typing import TYPE_CHECKING
18
18
19
19
from structlog import get_logger
20
+ from twisted .internet .base import DelayedCall
20
21
from twisted .internet .interfaces import IDelayedCall
21
22
from twisted .internet .task import LoopingCall
22
23
35
36
36
37
logger = get_logger ()
37
38
38
- # Number of seconds to wait for a sync to finish before trying to produce blocks
39
- _WAIT_SYNC_DELAY : int = 30
40
-
41
39
# Number of seconds used between each signer depending on its distance to the expected signer
42
- _SIGNER_TURN_INTERVAL : int = 1
40
+ _SIGNER_TURN_INTERVAL : int = 10
43
41
44
42
45
43
class PoaBlockProducer :
@@ -54,8 +52,6 @@ class PoaBlockProducer:
54
52
'_reactor' ,
55
53
'_manager' ,
56
54
'_poa_signer' ,
57
- '_started_producing' ,
58
- '_start_producing_lc' ,
59
55
'_schedule_block_lc' ,
60
56
'_last_seen_best_block' ,
61
57
'_delayed_call' ,
@@ -71,10 +67,6 @@ def __init__(self, *, settings: HathorSettings, reactor: ReactorProtocol, poa_si
71
67
self ._poa_signer = poa_signer
72
68
self ._last_seen_best_block : Block | None = None
73
69
74
- self ._started_producing = False
75
- self ._start_producing_lc = LoopingCall (self ._start_producing )
76
- self ._start_producing_lc .clock = self ._reactor
77
-
78
70
self ._schedule_block_lc = LoopingCall (self ._schedule_block )
79
71
self ._schedule_block_lc .clock = self ._reactor
80
72
self ._delayed_call : IDelayedCall | None = None
@@ -89,13 +81,9 @@ def manager(self, manager: HathorManager) -> None:
89
81
self ._manager = manager
90
82
91
83
def start (self ) -> None :
92
- self ._start_producing_lc .start (_WAIT_SYNC_DELAY )
93
84
self ._schedule_block_lc .start (self ._settings .AVG_TIME_BETWEEN_BLOCKS )
94
85
95
86
def stop (self ) -> None :
96
- if self ._start_producing_lc .running :
97
- self ._start_producing_lc .stop ()
98
-
99
87
if self ._schedule_block_lc .running :
100
88
self ._schedule_block_lc .stop ()
101
89
@@ -113,21 +101,15 @@ def _get_signer_index(self, previous_block: Block) -> int | None:
113
101
except ValueError :
114
102
return None
115
103
116
- def _start_producing (self ) -> None :
117
- """Start producing new blocks ."""
104
+ def _schedule_block (self ) -> None :
105
+ """Schedule propagation of a new block ."""
118
106
if not self .manager .can_start_mining ():
119
107
# We're syncing, so we'll try again later
120
- self ._log .warn ('cannot start producing new blocks , node not synced' )
108
+ self ._log .info ('cannot produce new block , node not synced' )
121
109
return
122
110
123
- self ._log .info ('started producing new blocks' )
124
- self ._started_producing = True
125
- self ._start_producing_lc .stop ()
126
-
127
- def _schedule_block (self ) -> None :
128
- """Schedule propagation of a new block."""
129
111
previous_block = self .manager .tx_storage .get_best_block ()
130
- if not self . _started_producing or previous_block == self ._last_seen_best_block :
112
+ if previous_block == self ._last_seen_best_block :
131
113
return
132
114
133
115
self ._last_seen_best_block = previous_block
@@ -139,6 +121,15 @@ def _schedule_block(self) -> None:
139
121
expected_timestamp = self ._expected_block_timestamp (previous_block , signer_index )
140
122
propagation_delay = 0 if expected_timestamp < now else expected_timestamp - now
141
123
124
+ if self ._delayed_call and self ._delayed_call .active ():
125
+ from hathor .transaction .poa import PoaBlock
126
+ assert isinstance (self ._delayed_call , DelayedCall )
127
+ delayed_block = self ._delayed_call .args [0 ]
128
+ assert isinstance (delayed_block , PoaBlock )
129
+ if delayed_block .weight != poa .BLOCK_WEIGHT_IN_TURN :
130
+ # we only cancel our delayed block if it was out of turn
131
+ self ._delayed_call .cancel ()
132
+
142
133
self ._delayed_call = self ._reactor .callLater (propagation_delay , self ._produce_block , previous_block )
143
134
self ._log .debug (
144
135
'scheduling block production' ,
@@ -158,25 +149,29 @@ def _produce_block(self, previous_block: PoaBlock) -> None:
158
149
self ._poa_signer .sign_block (block )
159
150
block .update_hash ()
160
151
161
- self .manager .on_new_tx (block , propagate_to_peers = False , fails_silently = False )
162
- if not block .get_metadata ().voided_by :
163
- self .manager .connections .send_tx_to_peers (block )
164
-
165
- self ._log .debug (
152
+ self ._log .info (
166
153
'produced new block' ,
167
154
block = block .hash_hex ,
168
155
height = block .get_height (),
169
156
weight = block .weight ,
170
157
parent = block .get_block_parent_hash ().hex (),
171
158
voided = bool (block .get_metadata ().voided_by ),
172
159
)
160
+ self .manager .on_new_tx (block , propagate_to_peers = False , fails_silently = False )
161
+ if not block .get_metadata ().voided_by :
162
+ self .manager .connections .send_tx_to_peers (block )
163
+ self ._delayed_call = None
173
164
174
165
def _expected_block_timestamp (self , previous_block : Block , signer_index : int ) -> int :
175
166
"""Calculate the expected timestamp for a new block."""
176
167
height = previous_block .get_height () + 1
177
- expected_index = poa .in_turn_signer_index (settings = self ._poa_settings , height = height )
178
- signers = poa .get_active_signers (self ._poa_settings , height )
179
- index_distance = (signer_index - expected_index ) % len (signers )
180
- assert 0 <= index_distance < len (signers )
168
+ index_distance = poa .get_signer_index_distance (
169
+ settings = self ._poa_settings ,
170
+ signer_index = signer_index ,
171
+ height = height ,
172
+ )
181
173
delay = _SIGNER_TURN_INTERVAL * index_distance
174
+ if index_distance > 0 :
175
+ # if it's not our turn, we add a constant offset to the delay
176
+ delay += self ._settings .AVG_TIME_BETWEEN_BLOCKS
182
177
return previous_block .timestamp + self ._settings .AVG_TIME_BETWEEN_BLOCKS + delay
0 commit comments