diff --git a/docs/ENVIRONMENT.rst b/docs/ENVIRONMENT.rst index c489b9f..00a555f 100644 --- a/docs/ENVIRONMENT.rst +++ b/docs/ENVIRONMENT.rst @@ -8,6 +8,11 @@ given, the rest will have sensible defaults if not specified. Many of the defaults around resource usage are conservative; I encourage you to review them. +Note: by default the server will only serve to connections from the +same machine. To be accessible to other users across the internet you +must set **HOST** appropriately; see below. + + Required -------- @@ -53,6 +58,7 @@ The following are required if you use the `run` script: The username the server will run as. + Miscellaneous ------------- @@ -63,12 +69,6 @@ These environment variables are optional: Must be a *NET* from one of the **Coin** classes in `lib/coins.py`_. Defaults to `mainnet`. - Note: if you are using Bitcoin Core post the August 1st fork, you - should have NET be `bitcoin-segwit`, and if on the Bitcoin Cash - chain NET should be `mainnet`. - Note Bitcoin Core >= 0.13.1 requires a special *NET* for testnet: - `testnet-segwit`. - * **DB_ENGINE** Database engine for the UTXO and history database. The default is @@ -76,13 +76,6 @@ These environment variables are optional: install the appropriate python package for your engine. The value is not case sensitive. -* **REORG_LIMIT** - - The maximum number of blocks to be able to handle in a chain - reorganisation. ElectrumX retains some fairly compact undo - information for this many blocks in levelDB. The default is a - function of **COIN** and **NET**; for Bitcoin mainnet it is 200. - * **HOST** The host or IP address that the TCP and SSL servers will use when @@ -101,20 +94,25 @@ These environment variables are optional: If set then SSL_CERTFILE and SSL_KEYFILE must be defined and be filesystem paths to those SSL files. -* **RPC_PORT** +* **RPC_HOST** - ElectrumX will listen on this port for local RPC connections. - ElectrumX listens for RPC connections unless this is explicitly set - to blank. The default is appropriate for **COIN** and **NET** - (e.g., 8000 for Bitcoin mainnet) if not set. + The host or IP address that the RPC server will listen on and + defaults to `localhost`. To listen on multiple specific addresses + specify a comma-separated list. Servers with unusual networking + setups might want to specify e.g. `::1` or `127.0.0.1` explicitly + rather than defaulting to `localhost`. -* **EVENT_LOOP_POLICY** + An empty string (normally indicating all interfaces) is interpreted + as `localhost`, because allowing access to the server's RPC + interface to arbitrary connections aacross the internet is not a + good idea. - The name of an event loop policy to replace the default asyncio - policy, if any. At present only `uvloop` is accepted, in which case - you must have installed the `uvloop`_ Python package. +* **RPC_PORT** - If you are not sure what this means leave it unset. + ElectrumX will listen on this port for local RPC connections. + ElectrumX listens for RPC connections unless this is explicitly set + to blank. The default depends on **COIN** and **NET** (e.g., 8000 + for Bitcoin mainnet) if not set, as indicated in `lib/coins.py`_. * **DONATION_ADDRESS** @@ -159,6 +157,22 @@ These environment variables are optional: that **ANON_LOGS** is honoured. Defaults to 3600. Set to zero to suppress this logging. +* **REORG_LIMIT** + + The maximum number of blocks to be able to handle in a chain + reorganisation. ElectrumX retains some fairly compact undo + information for this many blocks in levelDB. The default is a + function of **COIN** and **NET**; for Bitcoin mainnet it is 200. + +* **EVENT_LOOP_POLICY** + + The name of an event loop policy to replace the default asyncio + policy, if any. At present only `uvloop` is accepted, in which case + you must have installed the `uvloop`_ Python package. + + If you are not sure what this means leave it unset. + + Resource Usage Limits --------------------- diff --git a/server/controller.py b/server/controller.py index 678c6c8..5d504d8 100644 --- a/server/controller.py +++ b/server/controller.py @@ -204,7 +204,7 @@ class Controller(util.LoggedClass): async def main_loop(self): '''Controller main loop.''' if self.env.rpc_port is not None: - await self.start_server('RPC', ('127.0.0.1', '::1'), + await self.start_server('RPC', self.env.cs_host(for_rpc=True), self.env.rpc_port) self.ensure_future(self.bp.main_loop()) self.ensure_future(self.wait_for_bp_catchup()) @@ -292,7 +292,7 @@ class Controller(util.LoggedClass): self.state = self.LISTENING env = self.env - host = env.cs_host() + host = env.cs_host(for_rpc=False) if env.tcp_port is not None: await self.start_server('TCP', host, env.tcp_port) if env.ssl_port is not None: @@ -317,7 +317,8 @@ class Controller(util.LoggedClass): self.header_cache.clear() # Make a copy; self.sessions can change whilst await-ing - sessions = [s for s in self.sessions if isinstance(s, self.coin.SESSIONCLS)] + sessions = [s for s in self.sessions + if isinstance(s, self.coin.SESSIONCLS)] for session in sessions: await session.notify(self.bp.db_height, touched) diff --git a/server/env.py b/server/env.py index f5a6c3d..3d6427d 100644 --- a/server/env.py +++ b/server/env.py @@ -31,8 +31,8 @@ class Env(lib_util.LoggedClass): self.obsolete(['UTXO_MB', 'HIST_MB', 'NETWORK']) self.db_dir = self.required('DB_DIRECTORY') self.daemon_url = self.required('DAEMON_URL') - coin_name = self.required('COIN') - network = self.default('NET', 'mainnet') + coin_name = self.required('COIN').strip() + network = self.default('NET', 'mainnet').strip() self.coin = Coin.lookup_coin_class(coin_name, network) self.cache_MB = self.integer('CACHE_MB', 1200) self.host = self.default('HOST', 'localhost') @@ -43,6 +43,7 @@ class Env(lib_util.LoggedClass): if self.ssl_port: self.ssl_certfile = self.required('SSL_CERTFILE') self.ssl_keyfile = self.required('SSL_KEYFILE') + self.rpc_host = self.default('RPC_HOST', 'localhost') self.rpc_port = self.integer('RPC_PORT', 8000) self.max_subscriptions = self.integer('MAX_SUBSCRIPTIONS', 10000) self.banner_file = self.default('BANNER_FILE', None) @@ -184,11 +185,20 @@ class Env(lib_util.LoggedClass): return uvloop.EventLoopPolicy() raise self.Error('unknown event loop policy "{}"'.format(policy)) - def cs_host(self): + def cs_host(self, *, for_rpc): '''Returns the 'host' argument to pass to asyncio's create_server call. The result can be a single host name string, a list of - host name strings, or an empty string to bind to all interfaces.''' - result = self.host.split(',') + host name strings, or an empty string to bind to all interfaces. + + If rpc is True the host to use for the RPC server is returned. + Otherwise the host to use for SSL/TCP servers is returned. + ''' + host = self.rpc_host if for_rpc else self.host + result = [part.strip() for part in host.split(',')] if len(result) == 1: result = result[0] + # An empty result indicates all interfaces, which is not + # permitted for the RPC server. + if for_rpc and not result: + result = 'localhost' return result diff --git a/server/peers.py b/server/peers.py index fa63d54..9578143 100644 --- a/server/peers.py +++ b/server/peers.py @@ -524,7 +524,7 @@ class PeerManager(util.LoggedClass): # Use our listening Host/IP for outgoing connections so our # peers see the correct source. - host = self.env.cs_host() + host = self.env.cs_host(for_rpc=False) if isinstance(host, list): host = host[0] local_addr = (host, None) if host else None diff --git a/tests/server/test_env.py b/tests/server/test_env.py index 5843722..9d101f9 100644 --- a/tests/server/test_env.py +++ b/tests/server/test_env.py @@ -82,8 +82,11 @@ def test_COIN_NET(): os.environ['NET'] = 'testnet' e = Env() assert e.coin == lib_coins.BitcoinCashTestnet + os.environ['NET'] = ' testnet ' + e = Env() + assert e.coin == lib_coins.BitcoinCashTestnet os.environ.pop('NET') - os.environ['COIN'] = 'Litecoin' + os.environ['COIN'] = ' Litecoin ' e = Env() assert e.coin == lib_coins.Litecoin os.environ['NET'] = 'testnet' @@ -97,10 +100,23 @@ def test_HOST(): assert_default('HOST', 'host', 'localhost') os.environ['HOST'] = '' e = Env() - assert e.cs_host() == '' + assert e.cs_host(for_rpc=False) == '' os.environ['HOST'] = '192.168.0.1,23.45.67.89' e = Env() - assert e.cs_host() == ['192.168.0.1', '23.45.67.89'] + assert e.cs_host(for_rpc=False) == ['192.168.0.1', '23.45.67.89'] + os.environ['HOST'] = '192.168.0.1 , 23.45.67.89 ' + e = Env() + assert e.cs_host(for_rpc=False) == ['192.168.0.1', '23.45.67.89'] + +def test_RPC_HOST(): + assert_default('RPC_HOST', 'rpc_host', 'localhost') + os.environ['RPC_HOST'] = '' + e = Env() + # Blank reverts to localhost + assert e.cs_host(for_rpc=True) == 'localhost' + os.environ['RPC_HOST'] = '127.0.0.1, ::1' + e = Env() + assert e.cs_host(for_rpc=True) == ['127.0.0.1', '::1'] def test_REORG_LIMIT(): assert_integer('REORG_LIMIT', 'reorg_limit',