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,21 @@ 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
+ try :
106
+ self ._unsafe_schedule_block ()
107
+ except Exception :
108
+ self ._log .exception ('error while scheduling block' )
109
+
110
+ def _unsafe_schedule_block (self ) -> None :
111
+ """Schedule propagation of a new block."""
118
112
if not self .manager .can_start_mining ():
119
113
# We're syncing, so we'll try again later
120
- self ._log .warn ('cannot start producing new blocks , node not synced' )
114
+ self ._log .info ('cannot produce new block , node not synced' )
121
115
return
122
116
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
117
previous_block = self .manager .tx_storage .get_best_block ()
130
- if not self . _started_producing or previous_block == self ._last_seen_best_block :
118
+ if previous_block == self ._last_seen_best_block :
131
119
return
132
120
133
121
self ._last_seen_best_block = previous_block
@@ -139,6 +127,15 @@ def _schedule_block(self) -> None:
139
127
expected_timestamp = self ._expected_block_timestamp (previous_block , signer_index )
140
128
propagation_delay = 0 if expected_timestamp < now else expected_timestamp - now
141
129
130
+ if self ._delayed_call and self ._delayed_call .active ():
131
+ from hathor .transaction .poa import PoaBlock
132
+ assert isinstance (self ._delayed_call , DelayedCall )
133
+ delayed_block = self ._delayed_call .args [0 ]
134
+ assert isinstance (delayed_block , PoaBlock )
135
+ if delayed_block .weight != poa .BLOCK_WEIGHT_IN_TURN :
136
+ # we only cancel our delayed block if it was out of turn
137
+ self ._delayed_call .cancel ()
138
+
142
139
self ._delayed_call = self ._reactor .callLater (propagation_delay , self ._produce_block , previous_block )
143
140
self ._log .debug (
144
141
'scheduling block production' ,
@@ -158,25 +155,29 @@ def _produce_block(self, previous_block: PoaBlock) -> None:
158
155
self ._poa_signer .sign_block (block )
159
156
block .update_hash ()
160
157
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 (
158
+ self ._log .info (
166
159
'produced new block' ,
167
160
block = block .hash_hex ,
168
161
height = block .get_height (),
169
162
weight = block .weight ,
170
163
parent = block .get_block_parent_hash ().hex (),
171
164
voided = bool (block .get_metadata ().voided_by ),
172
165
)
166
+ self .manager .on_new_tx (block , propagate_to_peers = False , fails_silently = False )
167
+ if not block .get_metadata ().voided_by :
168
+ self .manager .connections .send_tx_to_peers (block )
169
+ self ._delayed_call = None
173
170
174
171
def _expected_block_timestamp (self , previous_block : Block , signer_index : int ) -> int :
175
172
"""Calculate the expected timestamp for a new block."""
176
173
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 )
174
+ index_distance = poa .get_signer_index_distance (
175
+ settings = self ._poa_settings ,
176
+ signer_index = signer_index ,
177
+ height = height ,
178
+ )
181
179
delay = _SIGNER_TURN_INTERVAL * index_distance
180
+ if index_distance > 0 :
181
+ # if it's not our turn, we add a constant offset to the delay
182
+ delay += self ._settings .AVG_TIME_BETWEEN_BLOCKS
182
183
return previous_block .timestamp + self ._settings .AVG_TIME_BETWEEN_BLOCKS + delay
0 commit comments