Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: implement AbstractMiner class for hathor simulator #564

Merged
merged 4 commits into from
Apr 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions hathor/simulator/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,11 @@


from hathor.simulator.fake_connection import FakeConnection
from hathor.simulator.miner import MinerSimulator
from hathor.simulator.simulator import Simulator
from hathor.simulator.tx_generator import RandomTransactionGenerator

__all__ = [
'FakeConnection',
'MinerSimulator',
'RandomTransactionGenerator',
'Simulator',
]
Empty file.
65 changes: 65 additions & 0 deletions hathor/simulator/miner/abstract_miner.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Copyright 2023 Hathor Labs
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from abc import ABC, abstractmethod
from typing import Optional

from structlog import get_logger
from twisted.internet.interfaces import IDelayedCall

from hathor.manager import HathorManager
from hathor.pubsub import EventArguments, HathorEvents
from hathor.util import Random

logger = get_logger()


class AbstractMiner(ABC):
msbrogli marked this conversation as resolved.
Show resolved Hide resolved
"""Abstract class to represent miner simulators."""

_manager: HathorManager
_rng: Random
_delayed_call: Optional[IDelayedCall] = None

def __init__(self, manager: HathorManager, rng: Random):
self._manager = manager
self._rng = rng

self._clock = self._manager.reactor

self.log = logger.new()

def start(self) -> None:
"""Start mining blocks."""
self._manager.pubsub.subscribe(HathorEvents.NETWORK_NEW_TX_ACCEPTED, self._on_new_tx)

self._schedule_next_block()

def stop(self) -> None:
"""Stop mining blocks."""
if self._delayed_call:
self._delayed_call.cancel()
self._delayed_call = None

self._manager.pubsub.unsubscribe(HathorEvents.NETWORK_NEW_TX_ACCEPTED, self._on_new_tx)

@abstractmethod
def _on_new_tx(self, key: HathorEvents, args: EventArguments) -> None:
"""Called when a new tx or block is received."""
raise NotImplementedError

@abstractmethod
def _schedule_next_block(self):
"""Schedule the propagation of the next block, and propagate a block if it has been found."""
raise NotImplementedError
Original file line number Diff line number Diff line change
Expand Up @@ -14,93 +14,70 @@

from typing import TYPE_CHECKING

from structlog import get_logger

from hathor.conf import HathorSettings
from hathor.manager import HathorEvents
from hathor.simulator.miner.abstract_miner import AbstractMiner
from hathor.util import Random

if TYPE_CHECKING:
from hathor.manager import HathorManager
from hathor.pubsub import EventArguments

settings = HathorSettings()
logger = get_logger()


class MinerSimulator:
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):
"""
:param: hashpower: Number of hashes per second
"""
self.blocks_found = 0
self.manager = manager
self.hashpower = hashpower
self.clock = manager.reactor
self.block = None
self.delayedcall = None
self.log = logger.new()
self.rng = rng
super().__init__(manager, rng)

def start(self) -> None:
""" Start mining blocks.
"""
self.manager.pubsub.subscribe(HathorEvents.NETWORK_NEW_TX_ACCEPTED, self.on_new_tx)
self.schedule_next_block()
self._hashpower = hashpower
self._block = None

def stop(self) -> None:
""" Stop mining blocks.
"""
if self.delayedcall:
self.delayedcall.cancel()
self.delayedcall = None
self.manager.pubsub.unsubscribe(HathorEvents.NETWORK_NEW_TX_ACCEPTED, self.on_new_tx)

def on_new_tx(self, key: HathorEvents, args: 'EventArguments') -> None:
def _on_new_tx(self, key: HathorEvents, args: 'EventArguments') -> None:
""" Called when a new tx or block is received. It updates the current mining to the
new block.
"""
tx = args.tx
if not tx.is_block:
return
if not self.block:
if not self._block:
return

tips = tx.storage.get_best_block_tips()
if self.block.parents[0] not in tips:
if self._block.parents[0] not in tips:
# Head changed
self.block = None
self.schedule_next_block()
self._block = None
self._schedule_next_block()

def schedule_next_block(self):
""" Schedule the propagation of the next block, and propagate a block if it has been found.
"""
if self.block:
self.block.nonce = self.rng.getrandbits(32)
self.block.update_hash()
self.blocks_found += 1
self.log.debug('randomized step: found new block', hash=self.block.hash_hex, nonce=self.block.nonce)
self.manager.propagate_tx(self.block, fails_silently=False)
self.block = None
def _schedule_next_block(self):
if self._block:
self._block.nonce = self._rng.getrandbits(32)
self._block.update_hash()
self.log.debug('randomized step: found new block', hash=self._block.hash_hex, nonce=self._block.nonce)
self._manager.propagate_tx(self._block, fails_silently=False)
self._block = None

if self.manager.can_start_mining():
block = self.manager.generate_mining_block()
if self._manager.can_start_mining():
block = self._manager.generate_mining_block()
geometric_p = 2**(-block.weight)
trials = self.rng.geometric(geometric_p)
dt = 1.0 * trials / self.hashpower
self.block = block
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

if dt > settings.WEIGHT_DECAY_ACTIVATE_DISTANCE:
self.block = None
self._block = None
dt = settings.WEIGHT_DECAY_ACTIVATE_DISTANCE

if self.delayedcall and self.delayedcall.active():
self.delayedcall.cancel()
self.delayedcall = self.clock.callLater(dt, self.schedule_next_block)
if self._delayed_call and self._delayed_call.active():
self._delayed_call.cancel()
self._delayed_call = self._clock.callLater(dt, self._schedule_next_block)
6 changes: 3 additions & 3 deletions hathor/simulator/simulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
from hathor.p2p.peer_id import PeerId
from hathor.pubsub import PubSubManager
from hathor.simulator.clock import HeapClock
from hathor.simulator.miner import MinerSimulator
from hathor.simulator.miner.geometric_miner import GeometricMiner
from hathor.simulator.tx_generator import RandomTransactionGenerator
from hathor.transaction.genesis import _get_genesis_transactions_unsafe
from hathor.transaction.storage.memory_storage import TransactionMemoryStorage
Expand Down Expand Up @@ -177,8 +177,8 @@ def create_peer(self, network: Optional[str] = None, peer_id: Optional[PeerId] =
def create_tx_generator(self, peer: HathorManager, *args: Any, **kwargs: Any) -> RandomTransactionGenerator:
return RandomTransactionGenerator(peer, self.rng, *args, **kwargs)

def create_miner(self, peer: HathorManager, *args: Any, **kwargs: Any) -> MinerSimulator:
return MinerSimulator(peer, self.rng, *args, **kwargs)
def create_miner(self, peer: HathorManager, *args: Any, **kwargs: Any) -> GeometricMiner:
return GeometricMiner(peer, self.rng, *args, **kwargs)

def run_to_completion(self):
""" This will advance the test's clock until all calls scheduled are done.
Expand Down