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(cpu-mining): create CpuMiningService #803

Merged
merged 1 commit into from
Nov 6, 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
15 changes: 15 additions & 0 deletions hathor/builder/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
from hathor.feature_activation.feature_service import FeatureService
from hathor.indexes import IndexesManager, MemoryIndexesManager, RocksDBIndexesManager
from hathor.manager import HathorManager
from hathor.mining.cpu_mining_service import CpuMiningService
from hathor.p2p.manager import ConnectionsManager
from hathor.p2p.peer_id import PeerId
from hathor.pubsub import PubSubManager
Expand Down Expand Up @@ -111,6 +112,7 @@ def __init__(self) -> None:
self._bit_signaling_service: Optional[BitSignalingService] = None

self._daa: Optional[DifficultyAdjustmentAlgorithm] = None
self._cpu_mining_service: Optional[CpuMiningService] = None

self._vertex_verifiers: Optional[VertexVerifiers] = None
self._vertex_verifiers_builder: _VertexVerifiersBuilder | None = None
Expand Down Expand Up @@ -174,6 +176,7 @@ def build(self) -> BuildArtifacts:
bit_signaling_service = self._get_or_create_bit_signaling_service()
verification_service = self._get_or_create_verification_service()
daa = self._get_or_create_daa()
cpu_mining_service = self._get_or_create_cpu_mining_service()

if self._enable_address_index:
indexes.enable_address_index(pubsub)
Expand Down Expand Up @@ -211,6 +214,7 @@ def build(self) -> BuildArtifacts:
feature_service=feature_service,
bit_signaling_service=bit_signaling_service,
verification_service=verification_service,
cpu_mining_service=cpu_mining_service,
**kwargs
)

Expand Down Expand Up @@ -490,6 +494,12 @@ def _get_or_create_daa(self) -> DifficultyAdjustmentAlgorithm:

return self._daa

def _get_or_create_cpu_mining_service(self) -> CpuMiningService:
if self._cpu_mining_service is None:
self._cpu_mining_service = CpuMiningService()

return self._cpu_mining_service

def use_memory(self) -> 'Builder':
self.check_if_can_modify()
self._storage_type = StorageType.MEMORY
Expand Down Expand Up @@ -602,6 +612,11 @@ def set_daa(self, daa: DifficultyAdjustmentAlgorithm) -> 'Builder':
self._daa = daa
return self

def set_cpu_mining_service(self, cpu_mining_service: CpuMiningService) -> 'Builder':
self.check_if_can_modify()
self._cpu_mining_service = cpu_mining_service
return self

def set_reactor(self, reactor: Reactor) -> 'Builder':
self.check_if_can_modify()
self._reactor = reactor
Expand Down
4 changes: 4 additions & 0 deletions hathor/builder/cli_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
from hathor.feature_activation.feature_service import FeatureService
from hathor.indexes import IndexesManager, MemoryIndexesManager, RocksDBIndexesManager
from hathor.manager import HathorManager
from hathor.mining.cpu_mining_service import CpuMiningService
from hathor.p2p.manager import ConnectionsManager
from hathor.p2p.peer_id import PeerId
from hathor.p2p.utils import discover_hostname, get_genesis_short_hash
Expand Down Expand Up @@ -223,6 +224,8 @@ def create_manager(self, reactor: Reactor) -> HathorManager:
)
verification_service = VerificationService(verifiers=vertex_verifiers)

cpu_mining_service = CpuMiningService()

p2p_manager = ConnectionsManager(
reactor,
network=network,
Expand Down Expand Up @@ -256,6 +259,7 @@ def create_manager(self, reactor: Reactor) -> HathorManager:
feature_service=self.feature_service,
bit_signaling_service=bit_signaling_service,
verification_service=verification_service,
cpu_mining_service=cpu_mining_service
)

p2p_manager.set_manager(self.manager)
Expand Down
3 changes: 2 additions & 1 deletion hathor/cli/mining.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,10 @@ def signal_handler(sig, frame):


def worker(q_in, q_out):
from hathor.mining.cpu_mining_service import CpuMiningService
signal.signal(signal.SIGINT, signal_handler)
block, start, end, sleep_seconds = q_in.get()
block.start_mining(start, end, sleep_seconds=sleep_seconds)
CpuMiningService().start_mining(block, start=start, end=end, sleep_seconds=sleep_seconds)
q_out.put(block)


Expand Down
4 changes: 3 additions & 1 deletion hathor/cli/multisig_spend.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@

from argparse import ArgumentParser, Namespace

from hathor.mining.cpu_mining_service import CpuMiningService


def create_parser() -> ArgumentParser:
from hathor.cli.util import create_parser
Expand All @@ -36,7 +38,7 @@ def execute(args: Namespace) -> None:
input_data = MultiSig.create_input_data(bytes.fromhex(args.redeem_script), signatures)
tx.inputs[0].data = input_data

tx.resolve()
CpuMiningService().resolve(tx)
print('Transaction after POW: ', tx.get_struct().hex())


Expand Down
4 changes: 3 additions & 1 deletion hathor/cli/twin_tx.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@

import requests

from hathor.mining.cpu_mining_service import CpuMiningService


def create_parser() -> ArgumentParser:
from hathor.cli.util import create_parser
Expand Down Expand Up @@ -89,7 +91,7 @@ def execute(args: Namespace) -> None:
if args.weight:
twin.weight = args.weight

twin.resolve()
CpuMiningService().resolve(twin)
if args.human:
print(twin.to_json())
else:
Expand Down
3 changes: 3 additions & 0 deletions hathor/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
from hathor.feature_activation.feature import Feature
from hathor.feature_activation.feature_service import FeatureService
from hathor.mining import BlockTemplate, BlockTemplates
from hathor.mining.cpu_mining_service import CpuMiningService
from hathor.p2p.manager import ConnectionsManager
from hathor.p2p.peer_id import PeerId
from hathor.p2p.protocol import HathorProtocol
Expand Down Expand Up @@ -98,6 +99,7 @@ def __init__(self,
feature_service: FeatureService,
bit_signaling_service: BitSignalingService,
verification_service: VerificationService,
cpu_mining_service: CpuMiningService,
network: str,
hostname: Optional[str] = None,
wallet: Optional[BaseWallet] = None,
Expand Down Expand Up @@ -176,6 +178,7 @@ def __init__(self,
self._feature_service = feature_service
self._bit_signaling_service = bit_signaling_service
self.verification_service = verification_service
self.cpu_mining_service = cpu_mining_service

self.consensus_algorithm = consensus_algorithm

Expand Down
91 changes: 91 additions & 0 deletions hathor/mining/cpu_mining_service.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# 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.

import time
from typing import Callable, Optional

from hathor.transaction import BaseTransaction
from hathor.transaction.token_creation_tx import TokenCreationTransaction
from hathor.types import VertexId

MAX_NONCE = 2**32


class CpuMiningService:
def resolve(self, vertex: BaseTransaction, *, update_time: bool = False) -> bool:
"""Run a CPU mining looking for the nonce that solves the proof-of-work

The `vertex.weight` must be set before calling this method.

:param update_time: update timestamp every 2 seconds
:return: True if a solution was found
:rtype: bool
"""
hash_bytes = self.start_mining(vertex, update_time=update_time)

if hash_bytes:
vertex.hash = hash_bytes
metadata = getattr(vertex, '_metadata', None)
if metadata is not None and metadata.hash is not None:
metadata.hash = hash_bytes

if isinstance(vertex, TokenCreationTransaction):
vertex.tokens = [vertex.hash]

return True
else:
return False

@staticmethod
def start_mining(
vertex: BaseTransaction,
*,
start: int = 0,
end: int = MAX_NONCE,
sleep_seconds: float = 0.0,
update_time: bool = True,
should_stop: Callable[[], bool] = lambda: False
) -> Optional[VertexId]:
"""Starts mining until it solves the problem, i.e., finds the nonce that satisfies the conditions

:param start: beginning of the search interval
:param end: end of the search interval
:param sleep_seconds: the number of seconds it will sleep after each attempt
:param update_time: update timestamp every 2 seconds
:return The hash of the solved PoW or None when it is not found
"""
pow_part1 = vertex.calculate_hash1()
target = vertex.get_target()
vertex.nonce = start
last_time = time.time()
while vertex.nonce < end:
if update_time:
now = time.time()
if now - last_time > 2:
if should_stop():
return None
vertex.timestamp = int(now)
pow_part1 = vertex.calculate_hash1()
last_time = now
vertex.nonce = start

result = vertex.calculate_hash2(pow_part1.copy())
if int(result.hex(), vertex.HEX_BASE) < target:
return result
vertex.nonce += 1
if sleep_seconds > 0:
time.sleep(sleep_seconds)
if should_stop():
return None
return None
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

from structlog import get_logger

from hathor.mining.cpu_mining_service import CpuMiningService
from hathor.transaction import BaseTransaction
from hathor.verification.block_verifier import BlockVerifier
from hathor.verification.merge_mined_block_verifier import MergeMinedBlockVerifier
Expand All @@ -25,30 +26,37 @@
logger = get_logger()


def verify_pow(vertex: BaseTransaction) -> None:
def _verify_pow(vertex: BaseTransaction) -> None:
assert vertex.hash is not None
logger.new().debug('Skipping BaseTransaction.verify_pow() for simulator')
logger.new().debug('Skipping VertexVerifier.verify_pow() for simulator')


class SimulatorBlockVerifier(BlockVerifier):
@classmethod
def verify_pow(cls, vertex: BaseTransaction, *, override_weight: Optional[float] = None) -> None:
verify_pow(vertex)
_verify_pow(vertex)


class SimulatorMergeMinedBlockVerifier(MergeMinedBlockVerifier):
@classmethod
def verify_pow(cls, vertex: BaseTransaction, *, override_weight: Optional[float] = None) -> None:
verify_pow(vertex)
_verify_pow(vertex)


class SimulatorTransactionVerifier(TransactionVerifier):
@classmethod
def verify_pow(cls, vertex: BaseTransaction, *, override_weight: Optional[float] = None) -> None:
verify_pow(vertex)
_verify_pow(vertex)


class SimulatorTokenCreationTransactionVerifier(TokenCreationTransactionVerifier):
@classmethod
def verify_pow(cls, vertex: BaseTransaction, *, override_weight: Optional[float] = None) -> None:
verify_pow(vertex)
_verify_pow(vertex)


class SimulatorCpuMiningService(CpuMiningService):
def resolve(self, vertex: BaseTransaction, *, update_time: bool = False) -> bool:
vertex.update_hash()
logger.new().debug('Skipping CpuMiningService.resolve() for simulator')
return True
55 changes: 5 additions & 50 deletions hathor/simulator/simulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,14 @@
from hathor.p2p.peer_id import PeerId
from hathor.simulator.clock import HeapClock, MemoryReactorHeapClock
from hathor.simulator.miner.geometric_miner import GeometricMiner
from hathor.simulator.tx_generator import RandomTransactionGenerator
from hathor.simulator.verification import (
from hathor.simulator.patches import (
SimulatorBlockVerifier,
SimulatorCpuMiningService,
SimulatorMergeMinedBlockVerifier,
SimulatorTokenCreationTransactionVerifier,
SimulatorTransactionVerifier,
)
from hathor.simulator.tx_generator import RandomTransactionGenerator
from hathor.util import Random
from hathor.verification.verification_service import VertexVerifiers
from hathor.wallet import HDWallet
Expand All @@ -53,52 +54,6 @@


class Simulator:
# used to concilite monkeypatching and multiple instances
_patches_rc: int = 0

@classmethod
def _apply_patches(cls):
""" Applies global patches on modules that aren't easy/possible to configure otherwise.
Patches:
- disable Transaction.resolve method
"""
from hathor.transaction import BaseTransaction

def resolve(self: BaseTransaction, update_time: bool = True) -> bool:
self.update_hash()
logger.new().debug('Skipping BaseTransaction.resolve() for simulator')
return True

cls._original_resolve = BaseTransaction.resolve
BaseTransaction.resolve = resolve

@classmethod
def _remove_patches(cls):
""" Remove the patches previously applied.
"""
from hathor.transaction import BaseTransaction
BaseTransaction.resolve = cls._original_resolve

@classmethod
def _patches_rc_increment(cls):
""" This is used by when starting instances of Simulator to determine when to run _apply_patches"""
assert cls._patches_rc >= 0
cls._patches_rc += 1
if cls._patches_rc == 1:
# patches not yet applied
cls._apply_patches()

@classmethod
def _patches_rc_decrement(cls):
""" This is used by when stopping instances of Simulator to determine when to run _remove_patches"""
assert cls._patches_rc > 0
cls._patches_rc -= 1
if cls._patches_rc == 0:
# patches not needed anymore
cls._remove_patches()

def __init__(self, seed: Optional[int] = None):
self.log = logger.new()
if seed is None:
Expand All @@ -116,7 +71,6 @@ def start(self) -> None:
"""Has to be called before any other method can be called."""
assert not self._started
self._started = True
self._patches_rc_increment()
first_timestamp = self.settings.GENESIS_BLOCK_TIMESTAMP
dt = self.rng.randint(3600, 120 * 24 * 3600)
self._clock.advance(first_timestamp + dt)
Expand All @@ -126,7 +80,6 @@ def stop(self) -> None:
"""Can only stop after calling start, but it doesn't matter if it's paused or not"""
assert self._started
self._started = False
self._patches_rc_decrement()

def get_default_builder(self) -> Builder:
"""
Expand Down Expand Up @@ -161,6 +114,7 @@ def create_artifacts(self, builder: Optional[Builder] = None) -> BuildArtifacts:
wallet = HDWallet(gap_limit=2)
wallet._manually_initialize()

cpu_mining_service = SimulatorCpuMiningService()
daa = DifficultyAdjustmentAlgorithm(settings=self.settings)

artifacts = builder \
Expand All @@ -169,6 +123,7 @@ def create_artifacts(self, builder: Optional[Builder] = None) -> BuildArtifacts:
.set_wallet(wallet) \
.set_vertex_verifiers_builder(_build_vertex_verifiers) \
.set_daa(daa) \
.set_cpu_mining_service(cpu_mining_service) \
.build()

artifacts.manager.start()
Expand Down
Loading