Skip to content

Commit

Permalink
tests(feature-activation): implement reorg test (#660)
Browse files Browse the repository at this point in the history
  • Loading branch information
glevco authored Jun 20, 2023
1 parent 2aead42 commit 1d5227c
Show file tree
Hide file tree
Showing 6 changed files with 294 additions and 58 deletions.
15 changes: 4 additions & 11 deletions hathor/feature_activation/feature_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
# limitations under the License.

from hathor.feature_activation.feature import Feature
from hathor.feature_activation.model.criteria import Criteria
from hathor.feature_activation.model.feature_description import FeatureDescription
from hathor.feature_activation.model.feature_state import FeatureState
from hathor.feature_activation.settings import Settings as FeatureSettings
Expand Down Expand Up @@ -76,7 +75,10 @@ def _calculate_new_state(
) -> FeatureState:
"""Returns the new feature state based on the new block, the criteria, and the previous state."""
height = boundary_block.get_height()
criteria = self._get_criteria(feature=feature)
criteria = self._feature_settings.features.get(feature)

if not criteria:
return FeatureState.DEFINED

assert not boundary_block.is_genesis, 'cannot calculate new state for genesis'
assert height % self._feature_settings.evaluation_interval == 0, (
Expand Down Expand Up @@ -124,15 +126,6 @@ def _calculate_new_state(

raise ValueError(f'Unknown previous state: {previous_state}')

def _get_criteria(self, *, feature: Feature) -> Criteria:
"""Get the Criteria defined for a specific Feature."""
criteria = self._feature_settings.features.get(feature)

if not criteria:
raise ValueError(f"Criteria not defined for feature '{feature}'.")

return criteria

def get_bits_description(self, *, block: Block) -> dict[Feature, FeatureDescription]:
"""Returns the criteria definition and feature state for all features at a certain block."""
return {
Expand Down
3 changes: 2 additions & 1 deletion hathor/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -823,7 +823,7 @@ def _make_block_template(self, parent_block: Block, parent_txs: 'ParentTxs', cur
def generate_mining_block(self, timestamp: Optional[int] = None,
parent_block_hash: Optional[VertexId] = None,
data: bytes = b'', address: Optional[Address] = None,
merge_mined: bool = False) -> Union[Block, MergeMinedBlock]:
merge_mined: bool = False, signal_bits: int = 0) -> Union[Block, MergeMinedBlock]:
""" Generates a block ready to be mined. The block includes new issued tokens,
parents, and the weight.
Expand All @@ -840,6 +840,7 @@ def generate_mining_block(self, timestamp: Optional[int] = None,
merge_mined=merge_mined,
address=address or None, # XXX: because we allow b'' for explicit empty output script
data=data,
signal_bits=signal_bits
)
return block

Expand Down
9 changes: 5 additions & 4 deletions hathor/mining/block_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ def generate_minimaly_valid_block(self) -> BaseTransaction:
def generate_mining_block(self, rng: Random, merge_mined: bool = False, address: Optional[bytes] = None,
timestamp: Optional[int] = None, data: Optional[bytes] = None,
storage: Optional[TransactionStorage] = None, include_metadata: bool = False,
) -> Union[Block, MergeMinedBlock]:
signal_bits: int = 0) -> Union[Block, MergeMinedBlock]:
""" Generates a block by filling the template with the given options and random parents (if multiple choices).
Note that if a timestamp is given it will be coerced into the [timestamp_min, timestamp_max] range.
Expand All @@ -64,7 +64,7 @@ def generate_mining_block(self, rng: Random, merge_mined: bool = False, address:
tx_outputs = [TxOutput(self.reward, output_script)]
cls: Union[type['Block'], type['MergeMinedBlock']] = MergeMinedBlock if merge_mined else Block
block = cls(outputs=tx_outputs, parents=parents, timestamp=block_timestamp,
data=data or b'', storage=storage, weight=self.weight)
data=data or b'', storage=storage, weight=self.weight, signal_bits=signal_bits)
if include_metadata:
block._metadata = TransactionMetadata(height=self.height, score=self.score)
block.get_metadata(use_storage=False)
Expand Down Expand Up @@ -124,9 +124,10 @@ def choose_random_template(self, rng: Random) -> BlockTemplate:
def generate_mining_block(self, rng: Random, merge_mined: bool = False, address: Optional[bytes] = None,
timestamp: Optional[int] = None, data: Optional[bytes] = None,
storage: Optional[TransactionStorage] = None, include_metadata: bool = False,
) -> Union[Block, MergeMinedBlock]:
signal_bits: int = 0) -> Union[Block, MergeMinedBlock]:
""" Randomly choose a template and use that for generating a block, see BlockTemplate.generate_mining_block"""
return self.choose_random_template(rng).generate_mining_block(rng, merge_mined=merge_mined, address=address,
timestamp=timestamp, data=data,
storage=storage or self.storage,
include_metadata=include_metadata)
include_metadata=include_metadata,
signal_bits=signal_bits)
23 changes: 21 additions & 2 deletions hathor/simulator/miner/geometric_miner.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,23 @@ class GeometricMiner(AbstractMiner):
""" Simulate block mining with actually solving the block. It is supposed to be used
with Simulator class. The mining part is simulated using the geometrical distribution.
"""
def __init__(self, manager: 'HathorManager', rng: Random, *, hashpower: float):
def __init__(
self,
manager: 'HathorManager',
rng: Random,
*,
hashpower: float,
signal_bits: Optional[list[int]] = None
) -> None:
"""
:param: hashpower: Number of hashes per second
:param: signal_bits: a list of signal_bits to be used in each mined block, in order. If there are more mined
blocks than values provided, 0 is used.
"""
super().__init__(manager, rng)

self._hashpower = hashpower
self._signal_bits = signal_bits or []
self._block: Optional[Block] = None
self._blocks_found: int = 0

Expand All @@ -58,6 +68,15 @@ def _on_new_tx(self, key: HathorEvents, args: 'EventArguments') -> None:
self._block = None
self._schedule_next_block()

def _generate_mining_block(self) -> 'Block':
"""Generates a block ready to be mined."""
try:
signal_bits = self._signal_bits.pop(0)
except IndexError:
signal_bits = 0

return self._manager.generate_mining_block(signal_bits=signal_bits)

def _schedule_next_block(self):
if self._block:
self._block.nonce = self._rng.getrandbits(32)
Expand All @@ -68,7 +87,7 @@ def _schedule_next_block(self):
self._block = None

if self._manager.can_start_mining():
block = self._manager.generate_mining_block()
block = self._generate_mining_block()
geometric_p = 2**(-block.weight)
trials = self._rng.geometric(geometric_p)
dt = 1.0 * trials / self._hashpower
Expand Down
5 changes: 2 additions & 3 deletions tests/feature_activation/test_feature_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -446,10 +446,9 @@ def test_get_state_from_failed(block_mocks: list[Block], tx_storage: Transaction
def test_get_state_undefined_feature(block_mocks: list[Block], service: FeatureService) -> None:
block = block_mocks[10]

with pytest.raises(ValueError) as e:
service.get_state(block=block, feature=Feature.NOP_FEATURE_1)
result = service.get_state(block=block, feature=Feature.NOP_FEATURE_1)

assert str(e.value) == f"Criteria not defined for feature '{Feature.NOP_FEATURE_1}'."
assert result == FeatureState.DEFINED


def test_get_bits_description(tx_storage: TransactionStorage) -> None:
Expand Down
Loading

0 comments on commit 1d5227c

Please sign in to comment.