From 4adb93f8fe36ca63c83eff1f29ee673fa321f3b3 Mon Sep 17 00:00:00 2001 From: Marcelo Salhab Brogliato Date: Fri, 20 Oct 2023 11:14:06 -0500 Subject: [PATCH] fix(mining): Fix block template not checking for MAX_FUTURE_TIMESTAMP_ALLOWED --- hathor/exception.py | 10 ++++++++++ hathor/manager.py | 8 ++++++++ hathor/simulator/miner/geometric_miner.py | 21 ++++++++++++++------- tests/tx/test_mining.py | 16 ++++++++++++++-- 4 files changed, 46 insertions(+), 9 deletions(-) diff --git a/hathor/exception.py b/hathor/exception.py index f898b3a66..1d3d42547 100644 --- a/hathor/exception.py +++ b/hathor/exception.py @@ -23,6 +23,16 @@ class BuilderError(Exception): pass +class BlockTemplateError(Exception): + """Base class for exceptions generating block template.""" + pass + + +class BlockTemplateTimestampError(BlockTemplateError): + """Raised when there is no timestamp available to prepare a block template.""" + pass + + class InvalidNewTransaction(HathorError): """Raised when a new received tx/block is not valid. """ diff --git a/hathor/manager.py b/hathor/manager.py index 749384af6..149adbf3a 100644 --- a/hathor/manager.py +++ b/hathor/manager.py @@ -31,6 +31,7 @@ from hathor.consensus import ConsensusAlgorithm from hathor.event.event_manager import EventManager from hathor.exception import ( + BlockTemplateTimestampError, DoubleSpendingError, HathorError, InitializationError, @@ -807,6 +808,13 @@ def _make_block_template(self, parent_block: Block, parent_txs: 'ParentTxs', cur timestamp_max = min(timestamp_abs_max, timestamp_max_decay) else: timestamp_max = timestamp_abs_max + timestamp_max = min(timestamp_max, int(current_timestamp + self._settings.MAX_FUTURE_TIMESTAMP_ALLOWED)) + if timestamp_max < timestamp_min: + raise BlockTemplateTimestampError( + f'Unable to create a block template because there is no timestamp available. ' + f'(min={timestamp_min}, max={timestamp_max}) ' + f'(current_timestamp={current_timestamp})' + ) timestamp = min(max(current_timestamp, timestamp_min), timestamp_max) parent_block_metadata = parent_block.get_metadata() # this is the min weight to cause an increase of twice the WEIGHT_TOL, we make sure to generate a template with diff --git a/hathor/simulator/miner/geometric_miner.py b/hathor/simulator/miner/geometric_miner.py index 5a2173287..2dc8209d6 100644 --- a/hathor/simulator/miner/geometric_miner.py +++ b/hathor/simulator/miner/geometric_miner.py @@ -16,6 +16,7 @@ from typing import TYPE_CHECKING, Optional from hathor.conf.get_settings import get_settings +from hathor.exception import BlockTemplateTimestampError from hathor.manager import HathorEvents from hathor.simulator.miner.abstract_miner import AbstractMiner from hathor.util import Random @@ -96,13 +97,19 @@ def _schedule_next_block(self): self._block = None if self._manager.can_start_mining(): - block = self._generate_mining_block() - geometric_p = 2**(-block.weight) - trials = self._rng.geometric(geometric_p) - dt = 1.0 * trials / self._hashpower - self._block = block - self.log.debug('randomized step: start mining new block', dt=dt, parents=[h.hex() for h in block.parents], - block_timestamp=block.timestamp) + try: + block = self._generate_mining_block() + except BlockTemplateTimestampError: + dt = 5 # Try again in 5 seconds. + else: + geometric_p = 2**(-block.weight) + trials = self._rng.geometric(geometric_p) + dt = 1.0 * trials / self._hashpower + self._block = block + self.log.debug('randomized step: start mining new block', + dt=dt, + parents=[h.hex() for h in block.parents], + block_timestamp=block.timestamp) else: dt = 60 diff --git a/tests/tx/test_mining.py b/tests/tx/test_mining.py index 822231907..c4f2be906 100644 --- a/tests/tx/test_mining.py +++ b/tests/tx/test_mining.py @@ -37,13 +37,19 @@ def test_block_template_after_genesis(self) -> None: block_templates = manager.get_block_templates() self.assertEqual(len(block_templates), 1) + + timestamp_max = min( + 0xffffffff, + int(manager.reactor.seconds()) + self._settings.MAX_FUTURE_TIMESTAMP_ALLOWED + ) + self.assertEqual(block_templates[0], BlockTemplate( versions={0, 3}, reward=settings.INITIAL_TOKEN_UNITS_PER_BLOCK * 100, weight=1.0, timestamp_now=int(manager.reactor.seconds()), timestamp_min=settings.GENESIS_BLOCK_TIMESTAMP + 3, - timestamp_max=0xffffffff, # no limit for next block after genesis + timestamp_max=timestamp_max, # no limit for next block after genesis # parents=[tx.hash for tx in self.genesis_blocks + self.genesis_txs], parents=block_templates[0].parents, parents_any=[], @@ -60,13 +66,19 @@ def test_regular_block_template(self) -> None: block_templates = manager.get_block_templates() self.assertEqual(len(block_templates), 1) + + timestamp_max = min( + blocks[-1].timestamp + settings.MAX_DISTANCE_BETWEEN_BLOCKS - 1, + int(manager.reactor.seconds()) + self._settings.MAX_FUTURE_TIMESTAMP_ALLOWED + ) + self.assertEqual(block_templates[0], BlockTemplate( versions={0, 3}, reward=settings.INITIAL_TOKEN_UNITS_PER_BLOCK * 100, weight=1.0, timestamp_now=int(manager.reactor.seconds()), timestamp_min=blocks[-1].timestamp + 1, - timestamp_max=blocks[-1].timestamp + settings.MAX_DISTANCE_BETWEEN_BLOCKS - 1, + timestamp_max=timestamp_max, # parents=[blocks[-1].hash, self.genesis_txs[-1].hash, self.genesis_txs[-2].hash], parents=block_templates[0].parents, parents_any=[],