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
112 changes: 112 additions & 0 deletions extras/update_checkpoints.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
#!/usr/bin/env python
"""
Usage: update_checkpoints.py [-h] [-n NETWORK]

Helper script to update the config checkpoint list.

options:
-h, --help show this help message and exit
-n NETWORK, --network NETWORK
The network to update (default: mainnet)

For example:

$ ./extras/update_checkpoints.py
New checkpoints to add for mainnet:

4_800_000: 00000000000000000716b8d9e96591ba7cb2d02c3d2d1d98d514f41c240fdff7
4_900_000: 0000000000000000079b1c1ebf48d351a7d31dcc55c5b4cf79ade79089a20f5a
5_000_000: 000000000000000006c9167db1cc7e93fcf1c3014da6c6221390d03d1640c9b3

cp(4_800_000, bytes.fromhex('00000000000000000716b8d9e96591ba7cb2d02c3d2d1d98d514f41c240fdff7')),
cp(4_900_000, bytes.fromhex('0000000000000000079b1c1ebf48d351a7d31dcc55c5b4cf79ade79089a20f5a')),
cp(5_000_000, bytes.fromhex('000000000000000006c9167db1cc7e93fcf1c3014da6c6221390d03d1640c9b3')),

The output can then be copied and pasted into `hathor/conf/mainnet.yml` and `hathor/conf/mainnet.py`
"""

import requests
import yaml
import argparse

# Built-in network configurations
NETWORKS: dict[str, dict[str, str]] = {
'mainnet': {
'config_file': 'hathor/conf/mainnet.yml',
'node_url': 'https://node1.mainnet.hathor.network/v1a',
},
'testnet': {
'config_file': 'hathor/conf/testnet.yml',
'node_url': 'https://node1.golf.testnet.hathor.network/v1a',
},
# Add more networks as needed
}

CHECKPOINT_INTERVAL: int = 100_000


def get_latest_height(node_url: str) -> int:
"""Fetch the latest block height."""
response = requests.get(f'{node_url}/transaction?type=block&count=1')
response.raise_for_status()
return response.json()['transactions'][0]['height']


def get_hash_for_height(node_url: str, height: int) -> str:
"""Fetch the hash for a given block height."""
response = requests.get(f'{node_url}/block_at_height?height={height}')
response.raise_for_status()
return response.json()['block']['tx_id']


def load_checkpoints(config_file: str) -> dict[str, int]:
"""Load the checkpoints from the specified YAML config file."""
with open(config_file, 'r') as file:
data = yaml.safe_load(file)
return data.get('CHECKPOINTS', {})


def print_new_checkpoints(network_name: str) -> None:
"""Print new checkpoints for the specified network."""
if network_name not in NETWORKS:
print(f'Error: Unknown network {network_name}. Available networks: {", ".join(NETWORKS.keys())}')
return

# Get the network configuration
network_config = NETWORKS[network_name]
config_file = network_config['config_file']
node_url = network_config['node_url']

# Load existing checkpoints from the YAML file
current_checkpoints = load_checkpoints(config_file)

# Get the latest block height
latest_height = get_latest_height(node_url)

# Determine missing checkpoints
new_checkpoints = {}
for height in range(CHECKPOINT_INTERVAL, latest_height + 1, CHECKPOINT_INTERVAL):
if height not in current_checkpoints:
block_hash = get_hash_for_height(node_url, height)
new_checkpoints[height] = block_hash

# Print new checkpoints
if new_checkpoints:
print(f'New checkpoints to add for {network_name}:\n')
for height, block_hash in sorted(new_checkpoints.items()):
print(f' {height:_}: {block_hash}')
print()
for height, block_hash in sorted(new_checkpoints.items()):
print(f''' cp({height:_}, bytes.fromhex('{block_hash}')),''')
else:
print(f'No new checkpoints needed for {network_name}. All up to date.')


if __name__ == '__main__':
# Parse command-line arguments
parser = argparse.ArgumentParser(description='Helper script to update the config checkpoint list.')
parser.add_argument('-n', '--network', default='mainnet', help='The network to update (default: mainnet)')
args = parser.parse_args()

# Print new checkpoints for the specified network
print_new_checkpoints(args.network)
15 changes: 15 additions & 0 deletions hathor/builder/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,9 @@ def __init__(self) -> None:
self._poa_signer: PoaSigner | None = None
self._poa_block_producer: PoaBlockProducer | None = None

self._enable_ipv6: bool = False
self._disable_ipv4: bool = False

def build(self) -> BuildArtifacts:
if self.artifacts is not None:
raise ValueError('cannot call build twice')
Expand Down Expand Up @@ -426,6 +429,8 @@ def _get_or_create_p2p_manager(self) -> ConnectionsManager:
ssl=enable_ssl,
whitelist_only=False,
rng=self._rng,
enable_ipv6=self._enable_ipv6,
disable_ipv4=self._disable_ipv4,
)
SyncSupportLevel.add_factories(
self._get_or_create_settings(),
Expand Down Expand Up @@ -812,6 +817,16 @@ def disable_full_verification(self) -> 'Builder':
self._full_verification = False
return self

def enable_ipv6(self) -> 'Builder':
self.check_if_can_modify()
self._enable_ipv6 = True
return self

def disable_ipv4(self) -> 'Builder':
self.check_if_can_modify()
self._disable_ipv4 = True
return self

def set_soft_voided_tx_ids(self, soft_voided_tx_ids: set[bytes]) -> 'Builder':
self.check_if_can_modify()
self._soft_voided_tx_ids = soft_voided_tx_ids
Expand Down
15 changes: 10 additions & 5 deletions hathor/builder/cli_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,9 @@ def create_manager(self, reactor: Reactor) -> HathorManager:
pubsub = PubSubManager(reactor)

if self._args.x_enable_event_queue:
self.log.warn('--x-enable-event-queue is deprecated and will be removed, use --enable-event-queue instead')

if self._args.x_enable_event_queue or self._args.enable_event_queue:
self.event_ws_factory = EventWebsocketFactory(
peer_id=str(peer.id),
settings=settings,
Expand Down Expand Up @@ -270,8 +273,8 @@ def create_manager(self, reactor: Reactor) -> HathorManager:
full_verification = False
if self._args.x_full_verification:
self.check_or_raise(
not self._args.x_enable_event_queue,
'--x-full-verification cannot be used with --x-enable-event-queue'
not self._args.x_enable_event_queue and not self._args.enable_event_queue,
'--x-full-verification cannot be used with --enable-event-queue'
)
full_verification = True

Expand All @@ -282,8 +285,8 @@ def create_manager(self, reactor: Reactor) -> HathorManager:
execution_manager=execution_manager
)

if self._args.x_enable_event_queue:
self.log.info('--x-enable-event-queue flag provided. '
if self._args.x_enable_event_queue or self._args.enable_event_queue:
self.log.info('--enable-event-queue flag provided. '
'The events detected by the full node will be stored and can be retrieved by clients')

self.feature_service = FeatureService(settings=settings, tx_storage=tx_storage)
Expand Down Expand Up @@ -326,6 +329,8 @@ def create_manager(self, reactor: Reactor) -> HathorManager:
ssl=True,
whitelist_only=False,
rng=Random(),
enable_ipv6=self._args.x_enable_ipv6,
disable_ipv4=self._args.x_disable_ipv4,
)

vertex_handler = VertexHandler(
Expand Down Expand Up @@ -376,7 +381,7 @@ def create_manager(self, reactor: Reactor) -> HathorManager:
checkpoints=settings.CHECKPOINTS,
environment_info=get_environment_info(args=str(self._args), peer_id=str(peer.id)),
full_verification=full_verification,
enable_event_queue=self._args.x_enable_event_queue,
enable_event_queue=self._args.x_enable_event_queue or self._args.enable_event_queue,
bit_signaling_service=bit_signaling_service,
verification_service=verification_service,
cpu_mining_service=cpu_mining_service,
Expand Down
2 changes: 1 addition & 1 deletion hathor/builder/resources_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -307,7 +307,7 @@ def create_resources(self) -> server.Site:
ws_factory.subscribe(self.manager.pubsub)

# Event websocket resource
if self._args.x_enable_event_queue:
if self._args.x_enable_event_queue or self._args.enable_event_queue:
root.putChild(b'event_ws', WebSocketResource(self.event_ws_factory))
root.putChild(b'event', EventResource(self.manager._event_manager))

Expand Down
2 changes: 2 additions & 0 deletions hathor/cli/events_simulator/events_simulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ def execute(args: Namespace, reactor: 'ReactorProtocol') -> None:

forwarding_ws_factory.start(stream_id='simulator_stream_id')
scenario.simulate(simulator, manager)
assert manager.wallet is not None
log.info('final result', balances=manager.wallet.get_balance_per_address(simulator.settings.HATHOR_TOKEN_UID))
reactor.listenTCP(args.port, site)
reactor.run()

Expand Down
3 changes: 2 additions & 1 deletion hathor/cli/nginx_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -240,11 +240,12 @@ def generate_nginx_config(openapi: dict[str, Any], *, out_file: TextIO, rate_k:

server_open = f'''
upstream backend {{
server fullnode:8080;
server 127.0.0.1:8080;
}}

server {{
listen 80;
listen [::]:80;
server_name localhost;

# Look for client IP in the X-Forwarded-For header
Expand Down
34 changes: 30 additions & 4 deletions hathor/cli/run_node.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@ class RunNode:
('--x-sync-bridge', lambda args: bool(args.x_sync_bridge)),
('--x-sync-v1-only', lambda args: bool(args.x_sync_v1_only)),
('--x-sync-v2-only', lambda args: bool(args.x_sync_v2_only)),
('--x-enable-event-queue', lambda args: bool(args.x_enable_event_queue)),
('--x-asyncio-reactor', lambda args: bool(args.x_asyncio_reactor)),
('--x-ipython-kernel', lambda args: bool(args.x_ipython_kernel)),
]
Expand Down Expand Up @@ -93,7 +92,9 @@ def create_parser(cls) -> ArgumentParser:
help='Address to listen for new connections (eg: tcp:8000)')
parser.add_argument('--bootstrap', action='append', help='Address to connect to (eg: tcp:127.0.0.1:8000')
parser.add_argument('--status', type=int, help='Port to run status server')
parser.add_argument('--x-status-ipv6-interface', help='IPv6 interface to bind the status server')
parser.add_argument('--stratum', type=int, help='Port to run stratum server')
parser.add_argument('--x-stratum-ipv6-interface', help='IPv6 interface to bind the stratum server')
parser.add_argument('--data', help='Data directory')
storage = parser.add_mutually_exclusive_group()
storage.add_argument('--rocksdb-storage', action='store_true', help='Use RocksDB storage backend (default)')
Expand Down Expand Up @@ -144,7 +145,9 @@ def create_parser(cls) -> ArgumentParser:
sync_args.add_argument('--x-sync-bridge', action='store_true', help='Enable running both sync protocols.')
parser.add_argument('--x-localhost-only', action='store_true', help='Only connect to peers on localhost')
parser.add_argument('--x-rocksdb-indexes', action='store_true', help=SUPPRESS)
parser.add_argument('--x-enable-event-queue', action='store_true', help='Enable event queue mechanism')
parser.add_argument('--x-enable-event-queue', action='store_true',
help='Deprecated: use --enable-event-queue instead.')
parser.add_argument('--enable-event-queue', action='store_true', help='Enable event queue mechanism')
parser.add_argument('--peer-id-blacklist', action='extend', default=[], nargs='+', type=str,
help='Peer IDs to forbid connection')
parser.add_argument('--config-yaml', type=str, help='Configuration yaml filepath')
Expand All @@ -162,6 +165,10 @@ def create_parser(cls) -> ArgumentParser:
help='Log tx bytes for debugging')
parser.add_argument('--disable-ws-history-streaming', action='store_true',
help='Disable websocket history streaming API')
parser.add_argument('--x-enable-ipv6', action='store_true',
help='Enables listening on IPv6 interface and connecting to IPv6 peers')
parser.add_argument('--x-disable-ipv4', action='store_true',
help='Disables connecting to IPv4 peers')
return parser

def prepare(self, *, register_resources: bool = True) -> None:
Expand All @@ -181,6 +188,7 @@ def prepare(self, *, register_resources: bool = True) -> None:
print('Maximum number of open file descriptors is too low. Minimum required is 256.')
sys.exit(-2)

self.validate_args()
self.check_unsafe_arguments()
self.check_python_version()

Expand All @@ -202,7 +210,15 @@ def prepare(self, *, register_resources: bool = True) -> None:

if self._args.stratum:
assert self.manager.stratum_factory is not None
self.reactor.listenTCP(self._args.stratum, self.manager.stratum_factory)

if self._args.x_enable_ipv6:
interface = self._args.x_stratum_ipv6_interface or '::0'
# Linux by default will map IPv4 to IPv6, so listening only in the IPv6 interface will be
# enough to handle IPv4 connections. There is a kernel parameter that controls this behavior:
# https://sysctl-explorer.net/net/ipv6/bindv6only/
self.reactor.listenTCP(self._args.stratum, self.manager.stratum_factory, interface=interface)
else:
self.reactor.listenTCP(self._args.stratum, self.manager.stratum_factory)

from hathor.conf.get_settings import get_global_settings
settings = get_global_settings()
Expand All @@ -217,7 +233,12 @@ def prepare(self, *, register_resources: bool = True) -> None:
status_server = resources_builder.build()
if self._args.status:
assert status_server is not None
self.reactor.listenTCP(self._args.status, status_server)

if self._args.x_enable_ipv6:
interface = self._args.x_status_ipv6_interface or '::0'
self.reactor.listenTCP(self._args.status, status_server, interface=interface)
else:
self.reactor.listenTCP(self._args.status, status_server)

self.start_manager()

Expand Down Expand Up @@ -351,6 +372,11 @@ def run_sysctl_from_signal(self) -> None:
except SysctlRunnerException as e:
self.log.warn('[USR2] Error', errmsg=str(e))

def validate_args(self) -> None:
if self._args.x_disable_ipv4 and not self._args.x_enable_ipv6:
self.log.critical('You must enable IPv6 if you disable IPv4.')
sys.exit(-1)

def check_unsafe_arguments(self) -> None:
unsafe_args_found = []
for arg_cmdline, arg_test_fn in self.UNSAFE_ARGUMENTS:
Expand Down
5 changes: 5 additions & 0 deletions hathor/cli/run_node_args.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@ class RunNodeArgs(BaseModel, extra=Extra.allow):
listen: list[str]
bootstrap: Optional[list[str]]
status: Optional[int]
x_status_ipv6_interface: Optional[str]
stratum: Optional[int]
x_stratum_ipv6_interface: Optional[str]
data: Optional[str]
rocksdb_storage: bool
memory_storage: bool
Expand Down Expand Up @@ -74,6 +76,7 @@ class RunNodeArgs(BaseModel, extra=Extra.allow):
x_localhost_only: bool
x_rocksdb_indexes: bool
x_enable_event_queue: bool
enable_event_queue: bool
peer_id_blacklist: list[str]
config_yaml: Optional[str]
signal_support: set[Feature]
Expand All @@ -83,3 +86,5 @@ class RunNodeArgs(BaseModel, extra=Extra.allow):
nano_testnet: bool
log_vertex_bytes: bool
disable_ws_history_streaming: bool
x_enable_ipv6: bool
x_disable_ipv4: bool
14 changes: 14 additions & 0 deletions hathor/conf/mainnet.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,20 @@
cp(3_400_000, bytes.fromhex('000000000000000077242c961a0c6f708bc671a8372eb8b095311f091fddc6c3')),
cp(3_500_000, bytes.fromhex('000000000000000a34ba20552c3cae9549b9c5ca07f644cf005328c948aa54d8')),
cp(3_600_000, bytes.fromhex('000000000000000011031d9ff030cd9e6fe8a3766bbeda6f6337c40dd30fa65f')),
cp(3_700_000, bytes.fromhex('0000000000000006c6e7295efcf0929173cc47ece41afc652410b72f36cbeeda')),
cp(3_800_000, bytes.fromhex('00000000000000122f57d59c7f6736a83483dcf71c34978102d7e04ce4dc9a5d')),
cp(3_900_000, bytes.fromhex('00000000000000069edf3300d6c41451485d7aabdbea34425a2411b880e8a976')),
cp(4_000_000, bytes.fromhex('00000000000000043b11a6c86c3cdaf773a5183737f136e196e816f862e1e3ba')),
cp(4_100_000, bytes.fromhex('0000000000000020822d529b6fcd8611f5a174b1f44a6c478a2fec64a80233ad')),
cp(4_200_000, bytes.fromhex('00000000000000052ffc34875fab4e545bc9dc76f1212c4fdafab3b6d7a026cd')),
cp(4_300_000, bytes.fromhex('000000000000000e1ea2af0e25087c0977e944dd0ffdae5fdff54dda85ed95be')),
cp(4_400_000, bytes.fromhex('0000000000000000020dab883c57e21829b590ef61ff5230f5fdc9d572300945')),
cp(4_500_000, bytes.fromhex('00000000000000034d5ddf802a8ac8fbf17cf50747041e433d28d9f2bcb6ef02')),
cp(4_600_000, bytes.fromhex('000000000000000055bb4e5b6d942da13cb631f318cfdc292793f28ef8a338ca')),
cp(4_700_000, bytes.fromhex('000000000000000002ae1d75811b1050fc98ee7ef30c48cde117ebbb42f47e22')),
cp(4_800_000, bytes.fromhex('00000000000000000716b8d9e96591ba7cb2d02c3d2d1d98d514f41c240fdff7')),
cp(4_900_000, bytes.fromhex('0000000000000000079b1c1ebf48d351a7d31dcc55c5b4cf79ade79089a20f5a')),
cp(5_000_000, bytes.fromhex('000000000000000006c9167db1cc7e93fcf1c3014da6c6221390d03d1640c9b3')),
],
SOFT_VOIDED_TX_IDS=list(map(bytes.fromhex, [
'0000000012a922a6887497bed9c41e5ed7dc7213cae107db295602168266cd02',
Expand Down
14 changes: 14 additions & 0 deletions hathor/conf/mainnet.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,20 @@ CHECKPOINTS:
3_400_000: 000000000000000077242c961a0c6f708bc671a8372eb8b095311f091fddc6c3
3_500_000: 000000000000000a34ba20552c3cae9549b9c5ca07f644cf005328c948aa54d8
3_600_000: 000000000000000011031d9ff030cd9e6fe8a3766bbeda6f6337c40dd30fa65f
3_700_000: 0000000000000006c6e7295efcf0929173cc47ece41afc652410b72f36cbeeda
3_800_000: 00000000000000122f57d59c7f6736a83483dcf71c34978102d7e04ce4dc9a5d
3_900_000: 00000000000000069edf3300d6c41451485d7aabdbea34425a2411b880e8a976
4_000_000: 00000000000000043b11a6c86c3cdaf773a5183737f136e196e816f862e1e3ba
4_100_000: 0000000000000020822d529b6fcd8611f5a174b1f44a6c478a2fec64a80233ad
4_200_000: 00000000000000052ffc34875fab4e545bc9dc76f1212c4fdafab3b6d7a026cd
4_300_000: 000000000000000e1ea2af0e25087c0977e944dd0ffdae5fdff54dda85ed95be
4_400_000: 0000000000000000020dab883c57e21829b590ef61ff5230f5fdc9d572300945
4_500_000: 00000000000000034d5ddf802a8ac8fbf17cf50747041e433d28d9f2bcb6ef02
4_600_000: 000000000000000055bb4e5b6d942da13cb631f318cfdc292793f28ef8a338ca
4_700_000: 000000000000000002ae1d75811b1050fc98ee7ef30c48cde117ebbb42f47e22
4_800_000: 00000000000000000716b8d9e96591ba7cb2d02c3d2d1d98d514f41c240fdff7
4_900_000: 0000000000000000079b1c1ebf48d351a7d31dcc55c5b4cf79ade79089a20f5a
5_000_000: 000000000000000006c9167db1cc7e93fcf1c3014da6c6221390d03d1640c9b3

SOFT_VOIDED_TX_IDS:
- 0000000012a922a6887497bed9c41e5ed7dc7213cae107db295602168266cd02
Expand Down
Loading