Skip to content

Commit 4c664ff

Browse files
authored
Merge pull request #753 from HathorNetwork/chore/miner-simulator-flakiness
chore: improve miner simulator flakiness
2 parents 88eeb5c + bd5311f commit 4c664ff

File tree

3 files changed

+44
-11
lines changed

3 files changed

+44
-11
lines changed

hathor/simulator/miner/geometric_miner.py

+20
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
import math
1516
from typing import TYPE_CHECKING, Optional
1617

1718
from hathor.conf import HathorSettings
@@ -50,6 +51,7 @@ def __init__(
5051
self._signal_bits = signal_bits or []
5152
self._block: Optional[Block] = None
5253
self._blocks_found: int = 0
54+
self._blocks_before_pause: float = math.inf
5355

5456
def _on_new_tx(self, key: HathorEvents, args: 'EventArguments') -> None:
5557
""" Called when a new tx or block is received. It updates the current mining to the
@@ -81,12 +83,17 @@ def _generate_mining_block(self) -> 'Block':
8183
return block
8284

8385
def _schedule_next_block(self):
86+
if self._blocks_before_pause <= 0:
87+
self._delayed_call = None
88+
return
89+
8490
if self._block:
8591
self._block.nonce = self._rng.getrandbits(32)
8692
self._block.update_hash()
8793
self.log.debug('randomized step: found new block', hash=self._block.hash_hex, nonce=self._block.nonce)
8894
self._manager.propagate_tx(self._block, fails_silently=False)
8995
self._blocks_found += 1
96+
self._blocks_before_pause -= 1
9097
self._block = None
9198

9299
if self._manager.can_start_mining():
@@ -110,3 +117,16 @@ def _schedule_next_block(self):
110117

111118
def get_blocks_found(self) -> int:
112119
return self._blocks_found
120+
121+
def pause_after_exactly(self, *, n_blocks: int) -> None:
122+
"""
123+
Configure the miner to pause mining blocks after exactly `n_blocks` are propagated. If called more than once,
124+
will unpause the miner and pause again according to the new argument.
125+
126+
Use this instead of the `StopAfterNMinedBlocks` trigger if you need "exactly N blocks" behavior, instead of
127+
"at least N blocks".
128+
"""
129+
self._blocks_before_pause = n_blocks
130+
131+
if not self._delayed_call:
132+
self._delayed_call = self._clock.callLater(0, self._schedule_next_block)

hathor/simulator/trigger.py

+6-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,12 @@ def should_stop(self) -> bool:
3131

3232

3333
class StopAfterNMinedBlocks(Trigger):
34-
"""Stop the simulation after `miner` finds N blocks. Note that these blocks might be orphan."""
34+
"""
35+
Stop the simulation after `miner` finds at least N blocks. Note that these blocks might be orphan.
36+
37+
Use `miner.pause_after_exactly()` instead of this trigger if you need "exactly N blocks" behavior, instead of
38+
"at least N blocks".
39+
"""
3540
def __init__(self, miner: 'AbstractMiner', *, quantity: int) -> None:
3641
self.miner = miner
3742
self.quantity = quantity

tests/feature_activation/test_mining_simulation.py

+18-10
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626
from hathor.feature_activation.settings import Settings as FeatureSettings
2727
from hathor.mining.ws import MiningWebsocketFactory, MiningWebsocketProtocol
2828
from hathor.p2p.resources import MiningResource
29-
from hathor.simulator.trigger import StopAfterNMinedBlocks
3029
from hathor.transaction.resources import GetBlockTemplateResource
3130
from hathor.transaction.util import unpack, unpack_len
3231
from hathor.util import json_loadb
@@ -88,47 +87,56 @@ def test_signal_bits_in_mining(self) -> None:
8887
# At the beginning, all features are outside their signaling period, so none are signaled.
8988
expected_signal_bits = 0b0000
9089
assert self._get_ws_signal_bits(ws_transport) == [expected_signal_bits]
91-
self.simulator.run(3600, trigger=StopAfterNMinedBlocks(miner, quantity=1))
90+
miner.pause_after_exactly(n_blocks=1)
91+
self.simulator.run(3600)
9292
assert self._get_signal_bits_from_get_block_template(get_block_template_client) == expected_signal_bits
9393
assert self._get_signal_bits_from_mining(mining_client) == expected_signal_bits
9494
assert self._get_ws_signal_bits(ws_transport) == [expected_signal_bits]
9595

96-
self.simulator.run(3600, trigger=StopAfterNMinedBlocks(miner, quantity=6))
96+
miner.pause_after_exactly(n_blocks=6)
97+
self.simulator.run(3600)
9798
assert self._get_ws_signal_bits(ws_transport) == [expected_signal_bits] * 6
9899

99100
# At height=8, NOP_FEATURE_1 is signaling, so it's enabled by the default support.
100101
expected_signal_bits = 0b0001
101-
self.simulator.run(3600, trigger=StopAfterNMinedBlocks(miner, quantity=1))
102+
miner.pause_after_exactly(n_blocks=1)
103+
self.simulator.run(3600)
102104
assert self._get_signal_bits_from_get_block_template(get_block_template_client) == expected_signal_bits
103105
assert self._get_signal_bits_from_mining(mining_client) == expected_signal_bits
104106
assert self._get_ws_signal_bits(ws_transport) == [expected_signal_bits]
105107

106-
self.simulator.run(3600, trigger=StopAfterNMinedBlocks(miner, quantity=3))
108+
miner.pause_after_exactly(n_blocks=3)
109+
self.simulator.run(3600)
107110
assert self._get_ws_signal_bits(ws_transport) == [expected_signal_bits] * 3
108111

109112
# At height=12, NOP_FEATURE_2 is signaling, enabled by the user. NOP_FEATURE_1 also continues signaling.
110113
expected_signal_bits = 0b0101
111-
self.simulator.run(3600, trigger=StopAfterNMinedBlocks(miner, quantity=1))
114+
miner.pause_after_exactly(n_blocks=1)
115+
self.simulator.run(3600)
112116
assert self._get_signal_bits_from_get_block_template(get_block_template_client) == expected_signal_bits
113117
assert self._get_signal_bits_from_mining(mining_client) == expected_signal_bits
114118
assert self._get_ws_signal_bits(ws_transport) == [expected_signal_bits]
115119

116-
self.simulator.run(3600, trigger=StopAfterNMinedBlocks(miner, quantity=7))
120+
miner.pause_after_exactly(n_blocks=7)
121+
self.simulator.run(3600)
117122
assert self._get_ws_signal_bits(ws_transport) == [expected_signal_bits] * 7
118123

119124
# At height=20, NOP_FEATURE_1 stops signaling, and NOP_FEATURE_2 continues.
120125
expected_signal_bits = 0b0100
121-
self.simulator.run(3600, trigger=StopAfterNMinedBlocks(miner, quantity=1))
126+
miner.pause_after_exactly(n_blocks=1)
127+
self.simulator.run(3600)
122128
assert self._get_signal_bits_from_get_block_template(get_block_template_client) == expected_signal_bits
123129
assert self._get_signal_bits_from_mining(mining_client) == expected_signal_bits
124130
assert self._get_ws_signal_bits(ws_transport) == [expected_signal_bits]
125131

126-
self.simulator.run(3600, trigger=StopAfterNMinedBlocks(miner, quantity=3))
132+
miner.pause_after_exactly(n_blocks=3)
133+
self.simulator.run(3600)
127134
assert self._get_ws_signal_bits(ws_transport) == [expected_signal_bits] * 3
128135

129136
# At height=24, all features have left their signaling period and therefore none are signaled.
130137
expected_signal_bits = 0b0000
131-
self.simulator.run(3600, trigger=StopAfterNMinedBlocks(miner, quantity=1))
138+
miner.pause_after_exactly(n_blocks=1)
139+
self.simulator.run(3600)
132140
assert self._get_signal_bits_from_get_block_template(get_block_template_client) == expected_signal_bits
133141
assert self._get_signal_bits_from_mining(mining_client) == expected_signal_bits
134142
assert self._get_ws_signal_bits(ws_transport) == [expected_signal_bits]

0 commit comments

Comments
 (0)