Skip to content
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
6 changes: 3 additions & 3 deletions hathor/builder/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
from typing_extensions import assert_never

from hathor.checkpoint import Checkpoint
from hathor.conf.get_settings import get_global_settings
from hathor.conf.settings import HathorSettings as HathorSettingsType
from hathor.consensus import ConsensusAlgorithm
from hathor.consensus.poa import PoaBlockProducer, PoaSigner
Expand Down Expand Up @@ -346,7 +345,7 @@ def set_peer_id(self, peer_id: PeerId) -> 'Builder':
def _get_or_create_settings(self) -> HathorSettingsType:
"""Return the HathorSettings instance set on this builder, or a new one if not set."""
if self._settings is None:
self._settings = get_global_settings()
raise ValueError('settings not set')
return self._settings

def _get_reactor(self) -> Reactor:
Expand Down Expand Up @@ -422,7 +421,8 @@ def _get_or_create_p2p_manager(self) -> ConnectionsManager:
assert self._network is not None

self._p2p_manager = ConnectionsManager(
reactor,
settings=self._get_or_create_settings(),
reactor=reactor,
network=self._network,
my_peer=my_peer,
pubsub=self._get_or_create_pubsub(),
Expand Down
3 changes: 2 additions & 1 deletion hathor/builder/cli_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,8 @@ def create_manager(self, reactor: Reactor) -> HathorManager:
cpu_mining_service = CpuMiningService()

p2p_manager = ConnectionsManager(
reactor,
settings=settings,
reactor=reactor,
network=network,
my_peer=peer_id,
pubsub=pubsub,
Expand Down
33 changes: 32 additions & 1 deletion hathor/consensus/consensus_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,16 @@
# See the License for the specific language governing permissions and
# limitations under the License.

import hashlib
from abc import ABC, abstractmethod
from enum import Enum, unique
from typing import Annotated, Any, Literal, TypeAlias

from pydantic import Field, NonNegativeInt, validator
from pydantic import Field, NonNegativeInt, PrivateAttr, validator
from typing_extensions import override

from hathor.transaction import TxVersion
from hathor.util import json_dumpb
from hathor.utils.pydantic import BaseModel


Expand All @@ -31,6 +33,7 @@ class ConsensusType(str, Enum):

class _BaseConsensusSettings(ABC, BaseModel):
type: ConsensusType
_peer_hello_hash: str | None = PrivateAttr(default=None)

def is_pow(self) -> bool:
"""Return whether this is a Proof-of-Work consensus."""
Expand All @@ -49,6 +52,16 @@ def is_vertex_version_valid(self, version: TxVersion, include_genesis: bool = Fa
"""Return whether a `TxVersion` is valid for this consensus type."""
return version in self._get_valid_vertex_versions(include_genesis)

def get_peer_hello_hash(self) -> str | None:
"""Return a hash of consensus settings to be used in peer hello validation."""
if self._peer_hello_hash is None:
self._peer_hello_hash = self._calculate_peer_hello_hash()
return self._peer_hello_hash

def _calculate_peer_hello_hash(self) -> str | None:
"""Calculate a hash of consensus settings to be used in peer hello validation."""
return None


class PowSettings(_BaseConsensusSettings):
type: Literal[ConsensusType.PROOF_OF_WORK] = ConsensusType.PROOF_OF_WORK
Expand All @@ -62,6 +75,10 @@ def _get_valid_vertex_versions(self, include_genesis: bool) -> set[TxVersion]:
TxVersion.MERGE_MINED_BLOCK
}

@override
def get_peer_hello_hash(self) -> str | None:
return None


class PoaSignerSettings(BaseModel):
public_key: bytes
Expand All @@ -86,6 +103,13 @@ def _validate_end_height(cls, end_height: int | None, values: dict[str, Any]) ->

return end_height

def to_json_dict(self) -> dict[str, Any]:
"""Return this signer settings instance as a json dict."""
json_dict = self.dict()
# TODO: We can use a custom serializer to convert bytes to hex when we update to Pydantic V2.
json_dict['public_key'] = self.public_key.hex()
return json_dict


class PoaSettings(_BaseConsensusSettings):
type: Literal[ConsensusType.PROOF_OF_AUTHORITY] = ConsensusType.PROOF_OF_AUTHORITY
Expand Down Expand Up @@ -114,5 +138,12 @@ def _get_valid_vertex_versions(self, include_genesis: bool) -> set[TxVersion]:

return versions

@override
def _calculate_peer_hello_hash(self) -> str | None:
data = b''
for signer in self.signers:
data += json_dumpb(signer.to_json_dict())
return hashlib.sha256(data).digest().hex()


ConsensusSettings: TypeAlias = Annotated[PowSettings | PoaSettings, Field(discriminator='type')]
3 changes: 1 addition & 2 deletions hathor/consensus/poa/poa.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,9 @@ def verify_poa_signature(settings: PoaSettings, block: PoaBlock) -> InvalidSigna
"""Return whether the provided public key was used to sign the block Proof-of-Authority."""
from hathor.consensus.poa import PoaSigner
active_signers = get_active_signers(settings, block.get_height())
sorted_signers = sorted(active_signers)
hashed_poa_data = get_hashed_poa_data(block)

for signer_index, public_key_bytes in enumerate(sorted_signers):
for signer_index, public_key_bytes in enumerate(active_signers):
signer_id = PoaSigner.get_poa_signer_id(public_key_bytes)
if block.signer_id != signer_id:
# this is not our signer
Expand Down
3 changes: 1 addition & 2 deletions hathor/consensus/poa/poa_block_producer.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,9 +108,8 @@ def _get_signer_index(self, previous_block: Block) -> int | None:
public_key = self._poa_signer.get_public_key()
public_key_bytes = get_public_key_bytes_compressed(public_key)
active_signers = poa.get_active_signers(self._poa_settings, height)
sorted_signers = sorted(active_signers)
try:
return sorted_signers.index(public_key_bytes)
return active_signers.index(public_key_bytes)
except ValueError:
return None

Expand Down
31 changes: 19 additions & 12 deletions hathor/p2p/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from twisted.internet import protocol
from twisted.internet.interfaces import IAddress

from hathor.conf.settings import HathorSettings
from hathor.p2p.manager import ConnectionsManager
from hathor.p2p.peer_id import PeerId
from hathor.p2p.protocol import HathorLineReceiver
Expand All @@ -36,14 +37,16 @@ class HathorServerFactory(protocol.ServerFactory):
protocol: type[MyServerProtocol] = MyServerProtocol

def __init__(
self,
network: str,
my_peer: PeerId,
p2p_manager: ConnectionsManager,
*,
use_ssl: bool,
self,
network: str,
my_peer: PeerId,
p2p_manager: ConnectionsManager,
*,
settings: HathorSettings,
use_ssl: bool,
):
super().__init__()
self._settings = settings
self.network = network
self.my_peer = my_peer
self.p2p_manager = p2p_manager
Expand All @@ -57,6 +60,7 @@ def buildProtocol(self, addr: IAddress) -> MyServerProtocol:
p2p_manager=self.p2p_manager,
use_ssl=self.use_ssl,
inbound=True,
settings=self._settings
)
p.factory = self
return p
Expand All @@ -69,14 +73,16 @@ class HathorClientFactory(protocol.ClientFactory):
protocol: type[MyClientProtocol] = MyClientProtocol

def __init__(
self,
network: str,
my_peer: PeerId,
p2p_manager: ConnectionsManager,
*,
use_ssl: bool,
self,
network: str,
my_peer: PeerId,
p2p_manager: ConnectionsManager,
*,
settings: HathorSettings,
use_ssl: bool,
):
super().__init__()
self._settings = settings
self.network = network
self.my_peer = my_peer
self.p2p_manager = p2p_manager
Expand All @@ -90,6 +96,7 @@ def buildProtocol(self, addr: IAddress) -> MyClientProtocol:
p2p_manager=self.p2p_manager,
use_ssl=self.use_ssl,
inbound=False,
settings=self._settings
)
p.factory = self
return p
39 changes: 23 additions & 16 deletions hathor/p2p/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
from twisted.python.failure import Failure
from twisted.web.client import Agent

from hathor.conf.get_settings import get_global_settings
from hathor.conf.settings import HathorSettings
from hathor.p2p.entrypoint import Entrypoint
from hathor.p2p.netfilter.factory import NetfilterFactory
from hathor.p2p.peer_discovery import PeerDiscovery
Expand All @@ -45,7 +45,6 @@
from hathor.manager import HathorManager

logger = get_logger()
settings = get_global_settings()

# The timeout in seconds for the whitelist GET request
WHITELIST_REQUEST_TIMEOUT = 45
Expand Down Expand Up @@ -74,9 +73,6 @@ class PeerConnectionsMetrics(NamedTuple):
class ConnectionsManager:
""" It manages all peer-to-peer connections and events related to control messages.
"""
MAX_ENABLED_SYNC = settings.MAX_ENABLED_SYNC
SYNC_UPDATE_INTERVAL = settings.SYNC_UPDATE_INTERVAL
PEER_DISCOVERY_INTERVAL = settings.PEER_DISCOVERY_INTERVAL

class GlobalRateLimiter:
SEND_TIPS = 'NodeSyncTimestamp.send_tips'
Expand All @@ -92,18 +88,25 @@ class GlobalRateLimiter:

rate_limiter: RateLimiter

def __init__(self,
reactor: Reactor,
network: str,
my_peer: PeerId,
pubsub: PubSubManager,
ssl: bool,
rng: Random,
whitelist_only: bool) -> None:
def __init__(
self,
settings: HathorSettings,
reactor: Reactor,
network: str,
my_peer: PeerId,
pubsub: PubSubManager,
ssl: bool,
rng: Random,
whitelist_only: bool,
) -> None:
self.log = logger.new()
self._settings = settings
self.rng = rng
self.manager = None
self._settings = get_global_settings()

self.MAX_ENABLED_SYNC = settings.MAX_ENABLED_SYNC
self.SYNC_UPDATE_INTERVAL = settings.SYNC_UPDATE_INTERVAL
self.PEER_DISCOVERY_INTERVAL = settings.PEER_DISCOVERY_INTERVAL

self.reactor = reactor
self.my_peer = my_peer
Expand All @@ -125,8 +128,12 @@ def __init__(self,
# Factories.
from hathor.p2p.factory import HathorClientFactory, HathorServerFactory
self.use_ssl = ssl
self.server_factory = HathorServerFactory(self.network, self.my_peer, p2p_manager=self, use_ssl=self.use_ssl)
self.client_factory = HathorClientFactory(self.network, self.my_peer, p2p_manager=self, use_ssl=self.use_ssl)
self.server_factory = HathorServerFactory(
self.network, self.my_peer, p2p_manager=self, use_ssl=self.use_ssl, settings=self._settings
)
self.client_factory = HathorClientFactory(
self.network, self.my_peer, p2p_manager=self, use_ssl=self.use_ssl, settings=self._settings
)

# Global maximum number of connections.
self.max_connections: int = self._settings.PEER_MAX_CONNECTIONS
Expand Down
18 changes: 13 additions & 5 deletions hathor/p2p/protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
from twisted.protocols.basic import LineReceiver
from twisted.python.failure import Failure

from hathor.conf.get_settings import get_global_settings
from hathor.conf.settings import HathorSettings
from hathor.p2p.entrypoint import Entrypoint
from hathor.p2p.messages import ProtocolMessages
from hathor.p2p.peer_id import PeerId
Expand Down Expand Up @@ -91,9 +91,17 @@ class WarningFlags(str, Enum):
sync_version: Optional[SyncVersion] # version chosen to be used on this connection
capabilities: set[str] # capabilities received from the peer in HelloState

def __init__(self, network: str, my_peer: PeerId, p2p_manager: 'ConnectionsManager',
*, use_ssl: bool, inbound: bool) -> None:
self._settings = get_global_settings()
def __init__(
self,
network: str,
my_peer: PeerId,
p2p_manager: 'ConnectionsManager',
*,
settings: HathorSettings,
use_ssl: bool,
inbound: bool,
) -> None:
self._settings = settings
self.network = network
self.my_peer = my_peer
self.connections = p2p_manager
Expand Down Expand Up @@ -164,7 +172,7 @@ def change_state(self, state_enum: PeerState) -> None:
"""Called to change the state of the connection."""
if state_enum not in self._state_instances:
state_cls = state_enum.value
instance = state_cls(self)
instance = state_cls(self, self._settings)
instance.state_name = state_enum.name
self._state_instances[state_enum] = instance
new_state = self._state_instances[state_enum]
Expand Down
4 changes: 3 additions & 1 deletion hathor/p2p/states/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from structlog import get_logger
from twisted.internet.defer import Deferred

from hathor.conf.settings import HathorSettings
from hathor.p2p.messages import ProtocolMessages

if TYPE_CHECKING:
Expand All @@ -33,8 +34,9 @@ class BaseState:
Callable[[str], None] | Callable[[str], Deferred[None]] | Callable[[str], Coroutine[Deferred[None], Any, None]]
]

def __init__(self, protocol: 'HathorProtocol'):
def __init__(self, protocol: 'HathorProtocol', settings: HathorSettings):
self.log = logger.new(**protocol.get_logger_context())
self._settings = settings
self.protocol = protocol
self.cmd_map = {
ProtocolMessages.ERROR: self.handle_error,
Expand Down
10 changes: 5 additions & 5 deletions hathor/p2p/states/hello.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import hathor
from hathor.conf.get_settings import get_global_settings
from hathor.conf.settings import HathorSettings
from hathor.exception import HathorError
from hathor.p2p.messages import ProtocolMessages
from hathor.p2p.states.base import BaseState
Expand All @@ -32,9 +33,8 @@


class HelloState(BaseState):
def __init__(self, protocol: 'HathorProtocol') -> None:
super().__init__(protocol)
self._settings = get_global_settings()
def __init__(self, protocol: 'HathorProtocol', settings: HathorSettings) -> None:
super().__init__(protocol, settings)
self.log = logger.new(**protocol.get_logger_context())
self.cmd_map.update({
ProtocolMessages.HELLO: self.handle_hello,
Expand All @@ -56,7 +56,7 @@ def _get_hello_data(self) -> dict[str, Any]:
'remote_address': format_address(remote),
'genesis_short_hash': get_genesis_short_hash(),
'timestamp': protocol.node.reactor.seconds(),
'settings_dict': get_settings_hello_dict(),
'settings_dict': get_settings_hello_dict(self._settings),
'capabilities': protocol.node.capabilities,
}
if self.protocol.node.has_sync_version_capability():
Expand Down Expand Up @@ -150,7 +150,7 @@ def handle_hello(self, payload: str) -> None:

if 'settings_dict' in data:
# If settings_dict is sent we must validate it
settings_dict = get_settings_hello_dict()
settings_dict = get_settings_hello_dict(self._settings)
if data['settings_dict'] != settings_dict:
protocol.send_error_and_close_connection(
'Settings values are different. {}'.format(json_dumps(settings_dict))
Expand Down
Loading