Skip to content

Commit 7b24072

Browse files
committed
refactor(side-dag): general improvements
1 parent ef14b24 commit 7b24072

File tree

3 files changed

+50
-57
lines changed

3 files changed

+50
-57
lines changed

hathor/consensus/poa/poa_block_producer.py

+23-29
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from typing import TYPE_CHECKING
1818

1919
from structlog import get_logger
20+
from twisted.internet.base import DelayedCall
2021
from twisted.internet.interfaces import IDelayedCall
2122
from twisted.internet.task import LoopingCall
2223

@@ -35,11 +36,8 @@
3536

3637
logger = get_logger()
3738

38-
# Number of seconds to wait for a sync to finish before trying to produce blocks
39-
_WAIT_SYNC_DELAY: int = 30
40-
4139
# 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
4341

4442

4543
class PoaBlockProducer:
@@ -54,8 +52,6 @@ class PoaBlockProducer:
5452
'_reactor',
5553
'_manager',
5654
'_poa_signer',
57-
'_started_producing',
58-
'_start_producing_lc',
5955
'_schedule_block_lc',
6056
'_last_seen_best_block',
6157
'_delayed_call',
@@ -71,10 +67,6 @@ def __init__(self, *, settings: HathorSettings, reactor: ReactorProtocol, poa_si
7167
self._poa_signer = poa_signer
7268
self._last_seen_best_block: Block | None = None
7369

74-
self._started_producing = False
75-
self._start_producing_lc = LoopingCall(self._start_producing)
76-
self._start_producing_lc.clock = self._reactor
77-
7870
self._schedule_block_lc = LoopingCall(self._schedule_block)
7971
self._schedule_block_lc.clock = self._reactor
8072
self._delayed_call: IDelayedCall | None = None
@@ -89,13 +81,9 @@ def manager(self, manager: HathorManager) -> None:
8981
self._manager = manager
9082

9183
def start(self) -> None:
92-
self._start_producing_lc.start(_WAIT_SYNC_DELAY)
9384
self._schedule_block_lc.start(self._settings.AVG_TIME_BETWEEN_BLOCKS)
9485

9586
def stop(self) -> None:
96-
if self._start_producing_lc.running:
97-
self._start_producing_lc.stop()
98-
9987
if self._schedule_block_lc.running:
10088
self._schedule_block_lc.stop()
10189

@@ -113,21 +101,15 @@ def _get_signer_index(self, previous_block: Block) -> int | None:
113101
except ValueError:
114102
return None
115103

116-
def _start_producing(self) -> None:
117-
"""Start producing new blocks."""
104+
def _schedule_block(self) -> None:
105+
"""Schedule propagation of a new block."""
118106
if not self.manager.can_start_mining():
119107
# 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')
121109
return
122110

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."""
129111
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:
131113
return
132114

133115
self._last_seen_best_block = previous_block
@@ -139,6 +121,15 @@ def _schedule_block(self) -> None:
139121
expected_timestamp = self._expected_block_timestamp(previous_block, signer_index)
140122
propagation_delay = 0 if expected_timestamp < now else expected_timestamp - now
141123

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+
142133
self._delayed_call = self._reactor.callLater(propagation_delay, self._produce_block, previous_block)
143134
self._log.debug(
144135
'scheduling block production',
@@ -158,18 +149,18 @@ def _produce_block(self, previous_block: PoaBlock) -> None:
158149
self._poa_signer.sign_block(block)
159150
block.update_hash()
160151

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(
166153
'produced new block',
167154
block=block.hash_hex,
168155
height=block.get_height(),
169156
weight=block.weight,
170157
parent=block.get_block_parent_hash().hex(),
171158
voided=bool(block.get_metadata().voided_by),
172159
)
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
173164

174165
def _expected_block_timestamp(self, previous_block: Block, signer_index: int) -> int:
175166
"""Calculate the expected timestamp for a new block."""
@@ -179,4 +170,7 @@ def _expected_block_timestamp(self, previous_block: Block, signer_index: int) ->
179170
index_distance = (signer_index - expected_index) % len(signers)
180171
assert 0 <= index_distance < len(signers)
181172
delay = _SIGNER_TURN_INTERVAL * index_distance
173+
if index_distance > 0:
174+
# if it's not our turn, we add a constant offset to the delay
175+
delay += self._settings.AVG_TIME_BETWEEN_BLOCKS
182176
return previous_block.timestamp + self._settings.AVG_TIME_BETWEEN_BLOCKS + delay

tests/poa/test_poa_block_producer.py

+9-11
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,6 @@ def test_poa_block_producer_one_signer() -> None:
5757

5858
# when we can start mining, we start producing blocks
5959
manager.can_start_mining = Mock(return_value=True)
60-
reactor.advance(20)
6160

6261
# we produce our first block
6362
reactor.advance(10)
@@ -116,7 +115,6 @@ def test_poa_block_producer_two_signers() -> None:
116115

117116
# when we can start mining, we start producing blocks
118117
manager.can_start_mining = Mock(return_value=True)
119-
reactor.advance(20)
120118

121119
# we produce our first block
122120
reactor.advance(10)
@@ -144,14 +142,14 @@ def test_poa_block_producer_two_signers() -> None:
144142
manager.on_new_tx.reset_mock()
145143

146144
# haven't produced the third block yet
147-
reactor.advance(9)
145+
reactor.advance(29)
148146

149147
# we produce our third block
150-
reactor.advance(2)
148+
reactor.advance(1)
151149
manager.on_new_tx.assert_called_once()
152150
block3 = manager.on_new_tx.call_args.args[0]
153151
assert isinstance(block3, PoaBlock)
154-
assert block3.timestamp == block2.timestamp + 11
152+
assert block3.timestamp == block2.timestamp + 30
155153
assert block3.weight == poa.BLOCK_WEIGHT_OUT_OF_TURN
156154
assert block3.outputs == []
157155
assert block3.get_block_parent_hash() == block2.hash
@@ -161,15 +159,15 @@ def test_poa_block_producer_two_signers() -> None:
161159
@pytest.mark.parametrize(
162160
['previous_height', 'signer_index', 'expected_delay'],
163161
[
164-
(0, 0, 33),
162+
(0, 0, 90),
165163
(0, 1, 30),
166-
(0, 2, 31),
167-
(0, 3, 32),
164+
(0, 2, 70),
165+
(0, 3, 80),
168166
169-
(1, 0, 32),
170-
(1, 1, 33),
167+
(1, 0, 80),
168+
(1, 1, 90),
171169
(1, 2, 30),
172-
(1, 3, 31),
170+
(1, 3, 70),
173171
]
174172
)
175173
def test_expected_block_timestamp(previous_height: int, signer_index: int, expected_delay: int) -> None:

tests/poa/test_poa_simulation.py

+18-17
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ def test_one_producer_allowed(self) -> None:
117117

118118
# manager is allowed to produce blocks, so it does
119119
manager.allow_mining_without_peers()
120-
self.simulator.run(120)
120+
self.simulator.run(90)
121121
assert manager.tx_storage.get_block_count() == 10
122122

123123
_assert_height_weight_signer_id(
@@ -155,9 +155,9 @@ def test_two_producers(self) -> None:
155155
self.simulator.add_connection(connection)
156156

157157
# both managers are producing blocks
158-
self.simulator.run(125)
159-
assert manager1.tx_storage.get_block_count() == 16
160-
assert manager2.tx_storage.get_block_count() == 17
158+
self.simulator.run(100)
159+
assert manager1.tx_storage.get_block_count() == 15
160+
assert manager2.tx_storage.get_block_count() == 16
161161
assert manager1.tx_storage.get_best_block_tips() == manager2.tx_storage.get_best_block_tips()
162162

163163
_assert_height_weight_signer_id(
@@ -195,13 +195,10 @@ def test_two_producers(self) -> None:
195195

196196
if height % 2 == 0:
197197
# if the height is even, it's manager1's turn.
198-
# manager2 will produce its block too, but it'll be voided and not propagated.
199198
assert len(blocks_manager1) == 1
200-
assert len(blocks_manager2) == 2
201199
_assert_block_in_turn(blocks_manager1[0], signer1)
202200
else:
203201
# if the height is odd, the opposite happens
204-
assert len(blocks_manager1) == 2
205202
assert len(blocks_manager2) == 1
206203
_assert_block_in_turn(blocks_manager2[0], signer2)
207204

@@ -214,41 +211,45 @@ def test_producer_leave_and_comeback(self) -> None:
214211
# out of turn
215212
manager1 = self._get_manager(signer1)
216213
manager1.allow_mining_without_peers()
217-
self.simulator.run(60)
214+
self.simulator.run(50)
218215

219216
manager2 = self._get_manager(signer2)
220217
connection = FakeConnection(manager1, manager2)
221218
self.simulator.add_connection(connection)
222219
self.simulator.run(80)
223220

224221
manager2.stop()
225-
self.simulator.run(40)
222+
self.simulator.run(70)
226223

227224
manager2.start()
228225
self.simulator.run(30)
229226

230-
assert manager1.tx_storage.get_block_count() == 23
231-
assert manager2.tx_storage.get_block_count() == 23
227+
assert manager1.tx_storage.get_block_count() == 22
228+
assert manager2.tx_storage.get_block_count() == 22
232229
assert manager1.tx_storage.get_best_block_tips() == manager2.tx_storage.get_best_block_tips()
233230

234231
_assert_height_weight_signer_id(
235232
manager1.tx_storage.get_all_transactions(),
236233
[
234+
# Before manager2 joins, only manager1 produces blocks
237235
(1, poa.BLOCK_WEIGHT_OUT_OF_TURN, signer_id1),
238236
(2, poa.BLOCK_WEIGHT_IN_TURN, signer_id1),
239237
(3, poa.BLOCK_WEIGHT_OUT_OF_TURN, signer_id1),
240238
(4, poa.BLOCK_WEIGHT_IN_TURN, signer_id1),
241239
(5, poa.BLOCK_WEIGHT_OUT_OF_TURN, signer_id1),
242240
(6, poa.BLOCK_WEIGHT_IN_TURN, signer_id1),
241+
# When manager2 joins, both of them start taking turns
243242
(7, poa.BLOCK_WEIGHT_IN_TURN, signer_id2),
244243
(8, poa.BLOCK_WEIGHT_IN_TURN, signer_id1),
245244
(9, poa.BLOCK_WEIGHT_IN_TURN, signer_id2),
246245
(10, poa.BLOCK_WEIGHT_IN_TURN, signer_id1),
247246
(11, poa.BLOCK_WEIGHT_IN_TURN, signer_id2),
248247
(12, poa.BLOCK_WEIGHT_IN_TURN, signer_id1),
248+
# manager2 leaves so manager1 produces all the next blocks
249249
(13, poa.BLOCK_WEIGHT_OUT_OF_TURN, signer_id1),
250250
(14, poa.BLOCK_WEIGHT_IN_TURN, signer_id1),
251251
(15, poa.BLOCK_WEIGHT_OUT_OF_TURN, signer_id1),
252+
# manager2 comes back again, so both of them take turns again
252253
(16, poa.BLOCK_WEIGHT_IN_TURN, signer_id1),
253254
(17, poa.BLOCK_WEIGHT_IN_TURN, signer_id2),
254255
(18, poa.BLOCK_WEIGHT_IN_TURN, signer_id1),
@@ -272,7 +273,7 @@ def test_existing_storage(self) -> None:
272273
manager1 = artifacts1.manager
273274
manager1.allow_mining_without_peers()
274275

275-
self.simulator.run(80)
276+
self.simulator.run(50)
276277
assert manager1.tx_storage.get_block_count() == 6
277278

278279
_assert_height_weight_signer_id(
@@ -296,7 +297,7 @@ def test_existing_storage(self) -> None:
296297
manager2 = artifacts.manager
297298
manager2.allow_mining_without_peers()
298299

299-
self.simulator.run(80)
300+
self.simulator.run(60)
300301
assert manager2.tx_storage.get_block_count() == 12
301302

302303
_assert_height_weight_signer_id(
@@ -332,7 +333,7 @@ def test_new_signer_added(self) -> None:
332333
manager_1a = artifacts_1a.manager
333334
manager_1a.allow_mining_without_peers()
334335

335-
self.simulator.run(80)
336+
self.simulator.run(50)
336337
assert manager_1a.tx_storage.get_block_count() == 6
337338

338339
_assert_height_weight_signer_id(
@@ -358,7 +359,7 @@ def test_new_signer_added(self) -> None:
358359
manager_1b = artifacts_1b.manager
359360
manager_1b.allow_mining_without_peers()
360361

361-
self.simulator.run(80)
362+
self.simulator.run(90)
362363
assert manager_1b.tx_storage.get_block_count() == 11
363364

364365
# after we restart it, new blocks are alternating
@@ -383,7 +384,7 @@ def test_new_signer_added(self) -> None:
383384

384385
connection = FakeConnection(manager_1b, manager_2)
385386
self.simulator.add_connection(connection)
386-
self.simulator.run(40)
387+
self.simulator.run(60)
387388

388389
# it should sync to the same blockchain
389390
_assert_height_weight_signer_id(
@@ -463,7 +464,7 @@ def test_use_case(self) -> None:
463464

464465
manager = self._get_manager(signer)
465466
manager.allow_mining_without_peers()
466-
self.simulator.run(130)
467+
self.simulator.run(100)
467468
assert manager.tx_storage.get_block_count() == 11
468469

469470
_assert_height_weight_signer_id(

0 commit comments

Comments
 (0)