def start_sentry_if_possible(self, args: Namespace) -> None: """Start Sentry integration if possible.""" if not args.sentry_dsn: return self.log.info('Starting Sentry', dsn=args.sentry_dsn) try: import sentry_sdk from structlog_sentry import SentryProcessor # noqa: F401 except ModuleNotFoundError: self.log.error('Please use `poetry install -E sentry` for enabling Sentry.') sys.exit(-3) import hathor from hathor.conf import HathorSettings settings = HathorSettings() sentry_sdk.init( dsn=args.sentry_dsn, release=hathor.__version__, environment=settings.NETWORK_NAME, )
from contextlib import redirect_stdout from io import StringIO from structlog.testing import capture_logs from hathor.cli.multisig_spend import create_parser, execute from hathor.conf import HathorSettings from hathor.crypto.util import decode_address from hathor.transaction import Transaction, TxInput, TxOutput from hathor.transaction.scripts import create_output_script from hathor.wallet.base_wallet import WalletBalance, WalletOutputInfo from hathor.wallet.util import generate_multisig_address, generate_multisig_redeem_script, generate_signature from tests import unittest from tests.utils import add_blocks_unlock_reward, add_new_blocks settings = HathorSettings() class MultiSigSpendTest(unittest.TestCase): def setUp(self): super().setUp() self.network = 'testnet' self.manager = self.create_peer(self.network, unlock_wallet=True) self.public_keys = [ bytes.fromhex('0250bf5890c9c6e9b4ab7f70375d31b827d45d0b7b4e3ba1918bcbe71b412c11d7'), bytes.fromhex('02d83dd1e9e0ac7976704eedab43fe0b79309166a47d70ec3ce8bbb08b8414db46'), bytes.fromhex('02358c539fa7474bf12f774749d0e1b5a9bc6e50920464818ebdb0043b143ae2ba') ]
def prepare(self, args: Namespace) -> None: import hathor from hathor.conf import HathorSettings from hathor.manager import HathorManager, TestMode from hathor.p2p.peer_discovery import BootstrapPeerDiscovery, DNSPeerDiscovery from hathor.p2p.peer_id import PeerId from hathor.p2p.utils import discover_hostname from hathor.transaction import genesis from hathor.transaction.storage import ( TransactionStorage, TransactionCacheStorage, TransactionCompactStorage, TransactionMemoryStorage, ) from hathor.wallet import HDWallet, Wallet settings = HathorSettings() if args.recursion_limit: sys.setrecursionlimit(args.recursion_limit) if not args.peer: peer_id = PeerId() else: data = json.load(open(args.peer, 'r')) peer_id = PeerId.create_from_json(data) print('Hathor v{} (genesis {})'.format(hathor.__version__, genesis.GENESIS_HASH.hex()[:7])) print('My peer id is', peer_id.id) def create_wallet(): if args.wallet == 'hd': print('Using HDWallet') kwargs = { 'words': args.words, } if args.passphrase: wallet_passphrase = getpass.getpass( prompt='HD Wallet passphrase:') kwargs['passphrase'] = wallet_passphrase.encode() if args.data: kwargs['directory'] = args.data return HDWallet(**kwargs) elif args.wallet == 'keypair': print('Using KeyPairWallet') if args.data: wallet = Wallet(directory=args.data) else: wallet = Wallet() wallet.flush_to_disk_interval = 5 # seconds if args.unlock_wallet: wallet_passwd = getpass.getpass(prompt='Wallet password:'******'Invalid type for wallet') tx_storage: TransactionStorage if args.data: wallet_dir = args.data print('Using Wallet at {}'.format(wallet_dir)) if args.rocksdb_storage: from hathor.transaction.storage import TransactionRocksDBStorage tx_dir = os.path.join(args.data, 'tx.db') tx_storage = TransactionRocksDBStorage( path=tx_dir, with_index=(not args.cache)) print('Using TransactionRocksDBStorage at {}'.format(tx_dir)) else: tx_dir = os.path.join(args.data, 'tx') tx_storage = TransactionCompactStorage( path=tx_dir, with_index=(not args.cache)) print('Using TransactionCompactStorage at {}'.format(tx_dir)) if args.cache: tx_storage = TransactionCacheStorage(tx_storage, reactor) if args.cache_size: tx_storage.capacity = args.cache_size if args.cache_interval: tx_storage.interval = args.cache_interval print( 'Using TransactionCacheStorage, capacity {}, interval {}s'. format(tx_storage.capacity, tx_storage.interval)) tx_storage.start() else: # if using MemoryStorage, no need to have cache tx_storage = TransactionMemoryStorage() print('Using TransactionMemoryStorage') self.tx_storage = tx_storage if args.wallet: self.wallet = create_wallet() else: self.wallet = None if args.hostname and args.auto_hostname: print('You cannot use --hostname and --auto-hostname together.') sys.exit(-1) if not args.auto_hostname: hostname = args.hostname else: print('Trying to discover your hostname...') hostname = discover_hostname() if not hostname: print('Aborting because we could not discover your hostname.') print('Try again or run without --auto-hostname.') sys.exit(-1) print('Hostname discovered and set to {}'.format(hostname)) network = settings.NETWORK_NAME self.manager = HathorManager(reactor, peer_id=peer_id, network=network, hostname=hostname, tx_storage=self.tx_storage, wallet=self.wallet, wallet_index=args.wallet_index, stratum_port=args.stratum, min_block_weight=args.min_block_weight, ssl=True) if args.allow_mining_without_peers: self.manager.allow_mining_without_peers() dns_hosts = [] if settings.BOOTSTRAP_DNS: dns_hosts.extend(settings.BOOTSTRAP_DNS) if args.dns: dns_hosts.extend(args.dns) if dns_hosts: self.manager.add_peer_discovery(DNSPeerDiscovery(dns_hosts)) if args.bootstrap: self.manager.add_peer_discovery( BootstrapPeerDiscovery(args.bootstrap)) if args.test_mode_tx_weight: self.manager.test_mode = TestMode.TEST_TX_WEIGHT if self.wallet: self.wallet.test_mode = True for description in args.listen: self.manager.add_listen_address(description) self.start_manager() self.register_resources(args)
def register_resources(self, args: Namespace) -> None: from hathor.conf import HathorSettings from hathor.p2p.resources import AddPeersResource, MiningInfoResource, MiningResource, StatusResource from hathor.prometheus import PrometheusMetricsExporter from hathor.resources import ProfilerResource from hathor.transaction.resources import ( DashboardTransactionResource, DecodeTxResource, GraphvizLegacyResource, GraphvizFullResource, GraphvizNeighboursResource, PushTxResource, TipsHistogramResource, TipsResource, TransactionAccWeightResource, TransactionResource, ) from hathor.version_resource import VersionResource from hathor.wallet.resources import ( AddressResource, BalanceResource, HistoryResource, LockWalletResource, SendTokensResource, SignTxResource, StateWalletResource, UnlockWalletResource, ) from hathor.wallet.resources.thin_wallet import ( AddressHistoryResource, SendTokensResource as SendTokensThinResource, TokenHistoryResource, TokenResource) from hathor.wallet.resources.nano_contracts import ( NanoContractDecodeResource, NanoContractExecuteResource, NanoContractMatchValueResource, ) from hathor.websocket import HathorAdminWebsocketFactory, WebsocketStatsResource from hathor.stratum.resources import MiningStatsResource settings = HathorSettings() if args.prometheus: kwargs: Dict[str, Any] = {'metrics': self.manager.metrics} if args.data: kwargs['path'] = os.path.join(args.data, 'prometheus') else: raise ValueError( 'To run prometheus exporter you must have a data path') prometheus = PrometheusMetricsExporter(**kwargs) prometheus.start() if args.status: # TODO get this from a file. How should we do with the factory? root = Resource() wallet_resource = Resource() root.putChild(b'wallet', wallet_resource) thin_wallet_resource = Resource() root.putChild(b'thin_wallet', thin_wallet_resource) contracts_resource = Resource() wallet_resource.putChild(b'nano-contract', contracts_resource) p2p_resource = Resource() root.putChild(b'p2p', p2p_resource) graphviz = GraphvizLegacyResource(self.manager) # XXX: reach the resource through /graphviz/ too, previously it was a leaf so this wasn't a problem graphviz.putChild(b'', graphviz) for fmt in ['dot', 'pdf', 'png', 'jpg']: bfmt = fmt.encode('ascii') graphviz.putChild( b'full.' + bfmt, GraphvizFullResource(self.manager, format=fmt)) graphviz.putChild( b'neighbours.' + bfmt, GraphvizNeighboursResource(self.manager, format=fmt)) resources = ( (b'status', StatusResource(self.manager), root), (b'version', VersionResource(self.manager), root), (b'mining', MiningResource(self.manager), root), (b'getmininginfo', MiningInfoResource(self.manager), root), (b'decode_tx', DecodeTxResource(self.manager), root), (b'push_tx', PushTxResource(self.manager), root), (b'graphviz', graphviz, root), (b'tips-histogram', TipsHistogramResource(self.manager), root), (b'tips', TipsResource(self.manager), root), (b'transaction', TransactionResource(self.manager), root), (b'transaction_acc_weight', TransactionAccWeightResource(self.manager), root), (b'dashboard_tx', DashboardTransactionResource(self.manager), root), (b'profiler', ProfilerResource(self.manager), root), # /thin_wallet (b'address_history', AddressHistoryResource(self.manager), thin_wallet_resource), (b'send_tokens', SendTokensThinResource(self.manager), thin_wallet_resource), (b'token', TokenResource(self.manager), thin_wallet_resource), (b'token_history', TokenHistoryResource(self.manager), thin_wallet_resource), # /wallet/nano-contract (b'match-value', NanoContractMatchValueResource(self.manager), contracts_resource), (b'decode', NanoContractDecodeResource(self.manager), contracts_resource), (b'execute', NanoContractExecuteResource(self.manager), contracts_resource), # /p2p (b'peers', AddPeersResource(self.manager), p2p_resource), ) for url_path, resource, parent in resources: parent.putChild(url_path, resource) if self.manager.stratum_factory is not None: root.putChild(b'miners', MiningStatsResource(self.manager)) if self.wallet and args.wallet_enable_api: wallet_resources = ( # /wallet (b'balance', BalanceResource(self.manager), wallet_resource ), (b'history', HistoryResource(self.manager), wallet_resource), (b'address', AddressResource(self.manager), wallet_resource), (b'send_tokens', SendTokensResource(self.manager), wallet_resource), (b'sign_tx', SignTxResource(self.manager), wallet_resource), (b'unlock', UnlockWalletResource(self.manager), wallet_resource), (b'lock', LockWalletResource(self.manager), wallet_resource), (b'state', StateWalletResource(self.manager), wallet_resource), ) for url_path, resource, parent in wallet_resources: parent.putChild(url_path, resource) # Websocket resource ws_factory = HathorAdminWebsocketFactory( metrics=self.manager.metrics, wallet_index=self.manager.tx_storage.wallet_index) ws_factory.start() resource = WebSocketResource(ws_factory) root.putChild(b"ws", resource) ws_factory.subscribe(self.manager.pubsub) # Websocket stats resource root.putChild(b'websocket_stats', WebsocketStatsResource(ws_factory)) real_root = Resource() real_root.putChild(settings.API_VERSION_PREFIX.encode('ascii'), root) status_server = server.Site(real_root) reactor.listenTCP(args.status, status_server) # Set websocket factory in metrics self.manager.metrics.websocket_factory = ws_factory
def generate_nginx_config( openapi: Dict[str, Any], *, out_file: TextIO, rate_k: float = 1.0, fallback_visibility: Visibility = Visibility.PRIVATE) -> None: """ Entry point of the functionality provided by the cli """ from datetime import datetime from hathor.conf import HathorSettings settings = HathorSettings() api_prefix = settings.API_VERSION_PREFIX locations: Dict[str, Dict[str, Any]] = {} limit_rate_zones: List[RateLimitZone] = [] for path, params in openapi['paths'].items(): visibility, did_fallback = _get_visibility(params, fallback_visibility) if did_fallback: warn( f'Visibility not set for path `{path}`, falling back to {fallback_visibility}' ) if visibility is Visibility.PRIVATE: continue location_params: Dict[str, Any] = { 'rate_limits': [], 'path_vars_re': params.get('x-path-params-regex', {}), } allowed_methods = {'OPTIONS'} for method in 'get post put patch delete head options trace'.split(): if method not in params: continue method_params = params[method] method_visibility, _ = _get_visibility(method_params, Visibility.PUBLIC) if method_visibility is Visibility.PRIVATE: continue allowed_methods.add(method.upper()) location_params['allowed_methods'] = sorted(allowed_methods) if not allowed_methods: warn(f'Path `{path}` has no public methods but is public') continue rate_limits = params.get('x-rate-limit') if not rate_limits: continue path_key = path.lower().replace('/', '__').replace('.', '__').replace( '{', '').replace('}', '') global_rate_limits = rate_limits.get('global', []) for i, rate_limit in enumerate(global_rate_limits): # zone, for top level `limit_req_zone` name = f'global{path_key}__{i}' # must match [a-z][a-z0-9_]* size = '32k' # min is 32k which is enough rate = _scale_rate_limit(rate_limit['rate'], rate_k) zone = RateLimitZone(name, '$global_key', size, rate) limit_rate_zones.append(zone) # limit, for location level `limit_req` burst = rate_limit.get('burst') delay = rate_limit.get('delay') location_params['rate_limits'].append( RateLimit(zone.name, burst, delay)) per_ip_rate_limits = rate_limits.get('per-ip', []) for i, rate_limit in enumerate(per_ip_rate_limits): name = f'per_ip{path_key}__{i}' # must match [a-z][a-z0-9_]* # zone, for top level `limit_req_zone` size = '10m' rate = _scale_rate_limit(rate_limit['rate'], rate_k) zone = RateLimitZone(name, '$per_ip_key', size, rate) limit_rate_zones.append(zone) # limit, for location level `limit_req` burst = rate_limit.get('burst') delay = rate_limit.get('delay') location_params['rate_limits'].append( RateLimit(zone.name, burst, delay)) locations[path] = location_params # TODO: consider using a templating engine # TODO: consider placing this parameters somewhere else # TODO: justify these limits: # XXX: global limit is based around performance testing on a c5.large+docker # XXX: per ip limit is based on the arbitrary choice of 5 clients per IP (to account for NATing) and a bit of # margin of 2 conns per client, in practice we will probably end up changing this up or down. # another possible reasoning considered was estimating the client capacity per node based on the parameters # already set on http endpoints, but the fact that request limiting can have bursts, and also that the rate of # global vs per ip vary a lot, this was nor persuited. websocket_max_conn_global = 4000 websocket_max_conn_per_ip = 10 mining_websocket_max_conn_global = 100 mining_websocket_max_conn_per_ip = 4 header = f'''# THIS FILE WAS AUTOGENERATED BY THE `hathor-cli nginx-config` TOOL AT {datetime.now()} server_tokens off; geo $should_limit {{ default 1; # Whitelist ELB IPs: 10.0.0.0/8 0; 172.16.0.0/12 0; 192.168.0.0/16 0; }} map $should_limit $per_ip_key {{ 0 ""; 1 $binary_remote_addr; }} map $should_limit $global_key {{ 0 ""; 1 "global"; }} limit_conn_zone $global_key zone=global__ws:32k; limit_conn_zone $per_ip_key zone=per_ip__ws:10m; limit_conn_zone $global_key zone=global__mining_ws:32k; limit_conn_zone $per_ip_key zone=per_ip__mining_ws:10m; ''' server_open = f''' upstream backend {{ server fullnode:8080; }} server {{ listen 80; server_name localhost; # Look for client IP in the X-Forwarded-For header real_ip_header X-Forwarded-For; # Ignore trusted IPs real_ip_recursive on; # Set ELB IP as trusted set_real_ip_from 10.0.0.0/8; set_real_ip_from 172.16.0.0/12; set_real_ip_from 192.168.0.0/16; # Trust CloudFront # See: https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/LocationsOfEdgeServers.html include set_real_ip_from_cloudfront; client_max_body_size 10M; limit_req_status 429; limit_conn_status 429; error_page 429 @429; location @429 {{ include cors_params; try_files /path/doesnt/matter =429; }} location ~ ^/{api_prefix}/ws/?$ {{ limit_conn global__ws {websocket_max_conn_global}; limit_conn per_ip__ws {websocket_max_conn_per_ip}; include cors_params; include proxy_params; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_pass http://backend; }} location ~ ^/{api_prefix}/mining_ws/?$ {{ limit_conn global__mining_ws {mining_websocket_max_conn_global}; limit_conn per_ip__mining_ws {mining_websocket_max_conn_per_ip}; include cors_params; include proxy_params; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_pass http://backend; }}''' # TODO: maybe return 403 instead? server_close = f''' location /{api_prefix} {{ return 403; }} location / {{ return 404; }} }} ''' out_file.write(header) # http level settings for zone in sorted(limit_rate_zones): out_file.write(zone.to_nginx_config()) out_file.write(server_open) # server level settings for location_path, location_params in locations.items(): location_path = location_path.replace( '.', r'\.').strip('/').format(**location_params['path_vars_re']) location_open = f''' location ~ ^/{api_prefix}/{location_path}/?$ {{ include cors_params; include proxy_params; ''' location_close = '''\ proxy_pass http://backend; }''' out_file.write(location_open) methods = ' '.join(location_params['allowed_methods']) out_file.write(' ' * 8 + f'limit_except {methods} {{ deny all; }}\n') for rate_limit in location_params.get('rate_limits', []): out_file.write(' ' * 8 + rate_limit.to_nginx_config()) out_file.write(location_close) out_file.write(server_close)
def check_unsafe_arguments(self, args: Namespace) -> None: unsafe_args_found = [] for arg_cmdline, arg_test_fn in self.UNSAFE_ARGUMENTS: if arg_test_fn(args): unsafe_args_found.append(arg_cmdline) if args.unsafe_mode is None: if unsafe_args_found: message = [ 'You need to enable --unsafe-mode to run with these arguments.', '', 'The following argument require unsafe mode:', ] for arg_cmdline in unsafe_args_found: message.append(arg_cmdline) message.extend([ '', 'Never enable UNSAFE MODE in a production environment.' ]) self.log.critical('\n'.join(message)) sys.exit(-1) else: fail = False message = [ 'UNSAFE MODE IS ENABLED', '', '********************************************************', '********************************************************', '', 'UNSAFE MODE IS ENABLED', '', 'You should never use --unsafe-mode in production environments.', '', ] from hathor.conf import HathorSettings settings = HathorSettings() if args.unsafe_mode != settings.NETWORK_NAME: message.extend([ f'Unsafe mode enabled for wrong network ({args.unsafe_mode} != {settings.NETWORK_NAME}).', '', ]) fail = True is_local_network = True if settings.NETWORK_NAME == 'mainnet': is_local_network = False elif settings.NETWORK_NAME.startswith('testnet'): is_local_network = False if not is_local_network: message.extend([ f'You should not enable unsafe mode on {settings.NETWORK_NAME} unless you know what you are doing', '', ]) if not unsafe_args_found: message.extend([ '--unsafe-mode is not needed because you have not enabled any unsafe feature.', '', 'Remove --unsafe-mode and try again.', ]) fail = True else: message.append('You have enabled the following features:') for arg_cmdline in unsafe_args_found: message.append(arg_cmdline) message.extend([ '', '********************************************************', '********************************************************', '', ]) self.log.critical('\n'.join(message)) if fail: sys.exit(-1)
def register_resources(self, args: Namespace) -> None: from hathor.conf import HathorSettings from hathor.debug_resources import ( DebugCrashResource, DebugLogResource, DebugMessAroundResource, DebugPrintResource, DebugRaiseResource, DebugRejectResource, ) from hathor.mining.ws import MiningWebsocketFactory from hathor.p2p.resources import AddPeersResource, MiningInfoResource, MiningResource, StatusResource from hathor.profiler import get_cpu_profiler from hathor.profiler.resources import CPUProfilerResource, ProfilerResource from hathor.prometheus import PrometheusMetricsExporter from hathor.transaction.resources import ( BlockAtHeightResource, CreateTxResource, DashboardTransactionResource, DecodeTxResource, GetBlockTemplateResource, GraphvizFullResource, GraphvizNeighboursResource, MempoolResource, PushTxResource, SubmitBlockResource, TransactionAccWeightResource, TransactionResource, TxParentsResource, ValidateAddressResource, ) from hathor.version_resource import VersionResource from hathor.wallet.resources import ( AddressResource, BalanceResource, HistoryResource, LockWalletResource, SendTokensResource, SignTxResource, StateWalletResource, UnlockWalletResource, ) from hathor.wallet.resources.nano_contracts import ( NanoContractDecodeResource, NanoContractExecuteResource, NanoContractMatchValueResource, ) from hathor.wallet.resources.thin_wallet import ( AddressBalanceResource, AddressHistoryResource, AddressSearchResource, SendTokensResource as SendTokensThinResource, TokenHistoryResource, TokenResource, ) from hathor.websocket import HathorAdminWebsocketFactory, WebsocketStatsResource settings = HathorSettings() cpu = get_cpu_profiler() if args.prometheus: kwargs: Dict[str, Any] = {'metrics': self.manager.metrics} if args.data: kwargs['path'] = os.path.join(args.data, 'prometheus') else: raise ValueError('To run prometheus exporter you must have a data path') prometheus = PrometheusMetricsExporter(**kwargs) prometheus.start() if args.status: # TODO get this from a file. How should we do with the factory? root = Resource() wallet_resource = Resource() root.putChild(b'wallet', wallet_resource) thin_wallet_resource = Resource() root.putChild(b'thin_wallet', thin_wallet_resource) contracts_resource = Resource() wallet_resource.putChild(b'nano-contract', contracts_resource) p2p_resource = Resource() root.putChild(b'p2p', p2p_resource) graphviz = Resource() # XXX: reach the resource through /graphviz/ too, previously it was a leaf so this wasn't a problem graphviz.putChild(b'', graphviz) for fmt in ['dot', 'pdf', 'png', 'jpg']: bfmt = fmt.encode('ascii') graphviz.putChild(b'full.' + bfmt, GraphvizFullResource(self.manager, format=fmt)) graphviz.putChild(b'neighbours.' + bfmt, GraphvizNeighboursResource(self.manager, format=fmt)) resources = [ (b'status', StatusResource(self.manager), root), (b'version', VersionResource(self.manager), root), (b'create_tx', CreateTxResource(self.manager), root), (b'decode_tx', DecodeTxResource(self.manager), root), (b'validate_address', ValidateAddressResource(self.manager), root), (b'push_tx', PushTxResource(self.manager, args.max_output_script_size, args.allow_non_standard_script), root), (b'graphviz', graphviz, root), (b'transaction', TransactionResource(self.manager), root), (b'block_at_height', BlockAtHeightResource(self.manager), root), (b'transaction_acc_weight', TransactionAccWeightResource(self.manager), root), (b'dashboard_tx', DashboardTransactionResource(self.manager), root), (b'profiler', ProfilerResource(self.manager), root), (b'top', CPUProfilerResource(self.manager, cpu), root), (b'mempool', MempoolResource(self.manager), root), # mining (b'mining', MiningResource(self.manager), root), (b'getmininginfo', MiningInfoResource(self.manager), root), (b'get_block_template', GetBlockTemplateResource(self.manager), root), (b'submit_block', SubmitBlockResource(self.manager), root), (b'tx_parents', TxParentsResource(self.manager), root), # /thin_wallet (b'address_history', AddressHistoryResource(self.manager), thin_wallet_resource), (b'address_balance', AddressBalanceResource(self.manager), thin_wallet_resource), (b'address_search', AddressSearchResource(self.manager), thin_wallet_resource), (b'send_tokens', SendTokensThinResource(self.manager), thin_wallet_resource), (b'token', TokenResource(self.manager), thin_wallet_resource), (b'token_history', TokenHistoryResource(self.manager), thin_wallet_resource), # /wallet/nano-contract (b'match-value', NanoContractMatchValueResource(self.manager), contracts_resource), (b'decode', NanoContractDecodeResource(self.manager), contracts_resource), (b'execute', NanoContractExecuteResource(self.manager), contracts_resource), # /p2p (b'peers', AddPeersResource(self.manager), p2p_resource), ] if args.enable_debug_api: debug_resource = Resource() root.putChild(b'_debug', debug_resource) resources.extend([ (b'log', DebugLogResource(), debug_resource), (b'raise', DebugRaiseResource(), debug_resource), (b'reject', DebugRejectResource(), debug_resource), (b'print', DebugPrintResource(), debug_resource), ]) if args.enable_crash_api: crash_resource = Resource() root.putChild(b'_crash', crash_resource) resources.extend([ (b'exit', DebugCrashResource(), crash_resource), (b'mess_around', DebugMessAroundResource(self.manager), crash_resource), ]) for url_path, resource, parent in resources: parent.putChild(url_path, resource) if self.manager.stratum_factory is not None: from hathor.stratum.resources import MiningStatsResource root.putChild(b'miners', MiningStatsResource(self.manager)) with_wallet_api = bool(self.wallet and args.wallet_enable_api) if with_wallet_api: wallet_resources = ( # /wallet (b'balance', BalanceResource(self.manager), wallet_resource), (b'history', HistoryResource(self.manager), wallet_resource), (b'address', AddressResource(self.manager), wallet_resource), (b'send_tokens', SendTokensResource(self.manager), wallet_resource), (b'sign_tx', SignTxResource(self.manager), wallet_resource), (b'unlock', UnlockWalletResource(self.manager), wallet_resource), (b'lock', LockWalletResource(self.manager), wallet_resource), (b'state', StateWalletResource(self.manager), wallet_resource), ) for url_path, resource, parent in wallet_resources: parent.putChild(url_path, resource) # Websocket resource assert self.manager.tx_storage.indexes is not None ws_factory = HathorAdminWebsocketFactory(metrics=self.manager.metrics, address_index=self.manager.tx_storage.indexes.addresses) ws_factory.start() root.putChild(b'ws', WebSocketResource(ws_factory)) # Mining websocket resource mining_ws_factory = MiningWebsocketFactory(self.manager) root.putChild(b'mining_ws', WebSocketResource(mining_ws_factory)) ws_factory.subscribe(self.manager.pubsub) # Websocket stats resource root.putChild(b'websocket_stats', WebsocketStatsResource(ws_factory)) real_root = Resource() real_root.putChild(settings.API_VERSION_PREFIX.encode('ascii'), root) from hathor.profiler.site import SiteProfiler status_server = SiteProfiler(real_root) reactor.listenTCP(args.status, status_server) self.log.info('with status', listen=args.status, with_wallet_api=with_wallet_api) # Set websocket factory in metrics self.manager.metrics.websocket_factory = ws_factory
def prepare(self, args: Namespace) -> None: import hathor from hathor.cli.util import check_or_exit from hathor.conf import HathorSettings from hathor.conf.get_settings import get_settings_module from hathor.daa import TestMode, _set_test_mode from hathor.manager import HathorManager from hathor.p2p.peer_discovery import BootstrapPeerDiscovery, DNSPeerDiscovery from hathor.p2p.peer_id import PeerId from hathor.p2p.utils import discover_hostname from hathor.transaction import genesis from hathor.transaction.storage import ( TransactionCacheStorage, TransactionCompactStorage, TransactionMemoryStorage, TransactionRocksDBStorage, TransactionStorage, ) from hathor.wallet import HDWallet, Wallet settings = HathorSettings() settings_module = get_settings_module() # only used for logging its location self.log = logger.new() from setproctitle import setproctitle setproctitle('{}hathor-core'.format(args.procname_prefix)) if args.recursion_limit: sys.setrecursionlimit(args.recursion_limit) else: sys.setrecursionlimit(5000) try: import resource except ModuleNotFoundError: pass else: (nofile_soft, _) = resource.getrlimit(resource.RLIMIT_NOFILE) if nofile_soft < 256: print('Maximum number of open file descriptors is too low. Minimum required is 256.') sys.exit(-2) if not args.peer: peer_id = PeerId() else: data = json.load(open(args.peer, 'r')) peer_id = PeerId.create_from_json(data) python = f'{platform.python_version()}-{platform.python_implementation()}' self.check_unsafe_arguments(args) self.log.info( 'hathor-core v{hathor}', hathor=hathor.__version__, pid=os.getpid(), genesis=genesis.GENESIS_HASH.hex()[:7], my_peer_id=str(peer_id.id), python=python, platform=platform.platform(), settings=settings_module.__file__, ) def create_wallet(): if args.wallet == 'hd': kwargs = { 'words': args.words, } if args.passphrase: wallet_passphrase = getpass.getpass(prompt='HD Wallet passphrase:') kwargs['passphrase'] = wallet_passphrase.encode() if args.data: kwargs['directory'] = args.data return HDWallet(**kwargs) elif args.wallet == 'keypair': print('Using KeyPairWallet') if args.data: wallet = Wallet(directory=args.data) else: wallet = Wallet() wallet.flush_to_disk_interval = 5 # seconds if args.unlock_wallet: wallet_passwd = getpass.getpass(prompt='Wallet password:'******'Invalid type for wallet') tx_storage: TransactionStorage if args.memory_storage: check_or_exit(not args.data, '--data should not be used with --memory-storage') # if using MemoryStorage, no need to have cache tx_storage = TransactionMemoryStorage() assert not args.x_rocksdb_indexes, 'RocksDB indexes require RocksDB data' self.log.info('with storage', storage_class=type(tx_storage).__name__) elif args.json_storage: check_or_exit(args.data, '--data is expected') assert not args.x_rocksdb_indexes, 'RocksDB indexes require RocksDB data' tx_storage = TransactionCompactStorage(path=args.data, with_index=(not args.cache)) else: check_or_exit(args.data, '--data is expected') if args.rocksdb_storage: self.log.warn('--rocksdb-storage is now implied, no need to specify it') cache_capacity = args.rocksdb_cache use_memory_indexes = not args.x_rocksdb_indexes tx_storage = TransactionRocksDBStorage(path=args.data, with_index=(not args.cache), cache_capacity=cache_capacity, use_memory_indexes=use_memory_indexes) self.log.info('with storage', storage_class=type(tx_storage).__name__, path=args.data) if args.cache: check_or_exit(not args.memory_storage, '--cache should not be used with --memory-storage') tx_storage = TransactionCacheStorage(tx_storage, reactor) if args.cache_size: tx_storage.capacity = args.cache_size if args.cache_interval: tx_storage.interval = args.cache_interval self.log.info('with cache', capacity=tx_storage.capacity, interval=tx_storage.interval) self.tx_storage = tx_storage self.log.info('with indexes', indexes_class=type(tx_storage.indexes).__name__) if args.wallet: self.wallet = create_wallet() self.log.info('with wallet', wallet=self.wallet, path=args.data) else: self.wallet = None if args.hostname and args.auto_hostname: print('You cannot use --hostname and --auto-hostname together.') sys.exit(-1) if not args.auto_hostname: hostname = args.hostname else: print('Trying to discover your hostname...') hostname = discover_hostname() if not hostname: print('Aborting because we could not discover your hostname.') print('Try again or run without --auto-hostname.') sys.exit(-1) print('Hostname discovered and set to {}'.format(hostname)) network = settings.NETWORK_NAME enable_sync_v1 = not args.x_sync_v2_only enable_sync_v2 = args.x_sync_v2_only or args.x_sync_bridge self.manager = HathorManager( reactor, peer_id=peer_id, network=network, hostname=hostname, tx_storage=self.tx_storage, wallet=self.wallet, wallet_index=args.wallet_index, stratum_port=args.stratum, ssl=True, checkpoints=settings.CHECKPOINTS, enable_sync_v1=enable_sync_v1, enable_sync_v2=enable_sync_v2, soft_voided_tx_ids=set(settings.SOFT_VOIDED_TX_IDS), ) if args.allow_mining_without_peers: self.manager.allow_mining_without_peers() if args.x_localhost_only: self.manager.connections.localhost_only = True dns_hosts = [] if settings.BOOTSTRAP_DNS: dns_hosts.extend(settings.BOOTSTRAP_DNS) if args.dns: dns_hosts.extend(args.dns) if dns_hosts: self.manager.add_peer_discovery(DNSPeerDiscovery(dns_hosts)) if args.bootstrap: self.manager.add_peer_discovery(BootstrapPeerDiscovery(args.bootstrap)) if args.test_mode_tx_weight: _set_test_mode(TestMode.TEST_TX_WEIGHT) if self.wallet: self.wallet.test_mode = True if args.x_full_verification: self.manager._full_verification = True if args.x_fast_init_beta: self.log.warn('--x-fast-init-beta is now the default, no need to specify it') for description in args.listen: self.manager.add_listen_address(description) self.start_manager(args) self.register_resources(args)