255 lines
14 KiB
Markdown
255 lines
14 KiB
Markdown
# Deploying plm-electrumx
|
|
|
|
What to set up to run a Palladium Electrum server with Docker, next to the Palladium node
|
|
running in Docker (service `palladiumd` on the Docker network `palladium`).
|
|
Quick start is in [README.md](README.md); this file covers ports, security and operation.
|
|
|
|
## Overview
|
|
|
|
```
|
|
Electrum clients ──50001 (tcp) / 50002 (ssl)──▶ electrumx ──palladiumd:2332 (RPC)──▶ palladiumd ◀── 2333 ──▶ Palladium network
|
|
container private "palladium" network container
|
|
```
|
|
|
|
The node and ElectrumX talk over the private Docker network `palladium`. The only things that must be
|
|
reachable from outside are the node's P2P port and the Electrum port(s) of this server.
|
|
|
|
## Node requirements
|
|
|
|
In `~/.palladium/palladium.conf` (restart the node container after a change):
|
|
|
|
```ini
|
|
server=1
|
|
txindex=1 # required: ElectrumX looks up any transaction by id
|
|
# prune must NOT be set: ElectrumX reads the whole chain
|
|
```
|
|
|
|
Start the node **before** ElectrumX: it creates the `palladium` network, which `docker-compose.yml` declares
|
|
as `external`. If the network is missing, `docker compose up` fails with `network palladium declared as external,
|
|
but could not be found`.
|
|
|
|
## RPC credentials
|
|
|
|
ElectrumX logs in to the node with `PLM_RPC_USER` / `PLM_RPC_PASSWORD` from `.env`. The defaults written by the node
|
|
(`rpcuser=username`) are only fine to try it. For a real server create a user of its own with
|
|
the `rpcauth.py` helper that comes with Bitcoin Core-based nodes (Palladium Core included):
|
|
|
|
```bash
|
|
python3 rpcauth.py electrumx # prints the rpcauth= line and the password (shown once)
|
|
```
|
|
|
|
Put the `rpcauth=...` line in `palladium.conf` (and remove `rpcuser`/`rpcpassword`), restart the node, and put the
|
|
user and password in `.env`. Anything on the `palladium` network can try to log in, so use a long password.
|
|
Keep `.env` private (`chmod 600 .env`); it is ignored by git.
|
|
|
|
## Ports
|
|
|
|
| Port | What | Open to | Notes |
|
|
| --- | --- | --- | --- |
|
|
| **50001/tcp** | Electrum protocol, plain | clients (internet, if the server is public) | published by `docker-compose.yml` |
|
|
| **50002/tcp** | Electrum protocol over SSL | clients | on by default, certificate generated automatically (see "SSL"); published by `docker-compose.yml` |
|
|
| **2333/tcp** | Palladium P2P (the node) | the internet, inbound | published by the node's compose file |
|
|
| 2332/tcp | node RPC | **only the `palladium` network** | never publish or forward it: it can spend wallet funds |
|
|
| 28332, 28334, 28335/tcp | node ZMQ | only the `palladium` network | never publish or forward them |
|
|
|
|
Changing the published ports: `ELECTRUMX_TCP_PORT` / `ELECTRUMX_SSL_PORT` in `.env` (host side only; the container always listens on 50001).
|
|
|
|
A server only for yourself (or for other containers) needs no open port: connect to `127.0.0.1:50001`, or join the
|
|
`palladium` network and use `electrumx:50001`.
|
|
|
|
### Firewall
|
|
|
|
- Docker publishes `ports:` by writing its own firewall rules, which **bypass `ufw` and `firewalld`**. A published
|
|
port is open to the internet even if `ufw` denies it. To keep a port private, bind it to an address, for example
|
|
`"127.0.0.1:50001:50001"` in `docker-compose.yml`.
|
|
- For a public server, allow what you want reachable: `sudo ufw allow 2333/tcp` (node) and the Electrum port(s).
|
|
Since Docker bypasses ufw for published ports, this matters mostly for services not run by Docker.
|
|
|
|
### Router (server behind NAT)
|
|
|
|
Give the server a fixed LAN address (DHCP reservation) and forward to it:
|
|
|
|
| External port | Protocol | To | Needed for |
|
|
| --- | --- | --- | --- |
|
|
| 2333 | TCP | `<server LAN IP>:2333` | the node (optional but recommended: more peers) |
|
|
| 50001 | TCP | `<server LAN IP>:50001` | a public Electrum server |
|
|
| 50002 | TCP | `<server LAN IP>:50002` | a public Electrum server with SSL |
|
|
|
|
Never forward 2332 or 28332/28334/28335. A server on a VPS with a public IP opens the ports in the provider's firewall instead.
|
|
|
|
## SSL (port 50002)
|
|
|
|
SSL is **on by default** and needs nothing to be set up: Electrum wallets prefer it, and plain TCP (50001) sends
|
|
addresses and balances unencrypted.
|
|
|
|
### What happens at start
|
|
|
|
`docker-entrypoint.sh` (the entrypoint of the image) does this at every start:
|
|
|
|
1. If you gave your own certificate (`SSL_CERTFILE` and `SSL_KEYFILE`, see below), it uses that.
|
|
2. Otherwise it looks for `server.crt` and `server.key` in `~/.plm-electrumx/ssl` (`/var/lib/electrumx-ssl` in the container).
|
|
- **They exist: they are used as they are, never overwritten.**
|
|
- **They do not exist (first start): a self-signed certificate valid 10 years is generated** for `SSL_HOSTNAME`
|
|
and saved there. If only one of the two files exists, the container stops with an error instead of overwriting it.
|
|
3. It prints the certificate subject, expiry date and SHA-256 fingerprint in the log
|
|
(`docker compose logs electrumx | grep entrypoint`), and warns if it expires in less than 30 days.
|
|
4. If `SERVICES` is not set, it sets it to `tcp://:50001,ssl://:50002`.
|
|
|
|
The compose file publishes 50002 (`ELECTRUMX_SSL_PORT`). Settings, all in `.env`:
|
|
|
|
| Variable | Default | Meaning |
|
|
| --- | --- | --- |
|
|
| `SSL_ENABLED` | `true` | `false`: no SSL, only `tcp://:50001` |
|
|
| `SSL_HOSTNAME` | `electrumx` | name (or IP) written in a generated certificate; set it to the name of the server (it matters only when the certificate is generated) |
|
|
| `SSL_CERTFILE`, `SSL_KEYFILE` | unset | your own certificate and key (paths inside the container); both or neither |
|
|
| `ELECTRUMX_SSL_PORT` | `50002` | host port published for SSL |
|
|
|
|
### Self-signed certificate: what clients see
|
|
|
|
A generated certificate is signed by nobody. Electrum wallets handle it by *trust on first use*: at the first connection
|
|
the wallet saves the certificate, and from then on it refuses a different one (as SSH does with host keys), so a
|
|
man-in-the-middle cannot take over later. The fields of the certificate (name, country, ...) are not checked by the
|
|
client, only the expiry date. Consequences:
|
|
|
|
- **Keep the certificate.** It is in `~/.plm-electrumx/ssl`, separate from the database (`~/.plm-electrumx/db`) on
|
|
purpose: the database can be deleted and rebuilt at any time, the certificate should not be. If `ssl/` is deleted,
|
|
a new certificate is generated and every client that saved the old one refuses to connect.
|
|
- **Back it up** by copying the folder (it contains the private key: keep it private):
|
|
```bash
|
|
cp -a ~/.plm-electrumx/ssl /safe/place/plm-electrumx-ssl
|
|
```
|
|
To restore it, copy the two files (`server.crt`, `server.key`) back into `~/.plm-electrumx/ssl` before the first
|
|
start, or use them as "your own certificate" below.
|
|
- If it expires or is lost, clients that saved it must be told to forget it, or the server must get a new name.
|
|
Generated certificates last 10 years, so expiry is rarely the problem.
|
|
- Whether a wallet accepts a self-signed certificate depends on the wallet. Electrum does (trust on first use);
|
|
check the wallet you intend to use.
|
|
|
|
### Your own certificate (for example Let's Encrypt)
|
|
|
|
With a certificate signed by a CA the client verifies it by itself, with no trust on first use, but it needs a domain
|
|
and must be renewed (about every 90 days).
|
|
|
|
1. Get the certificate and key (`certbot certonly --standalone -d electrum.example.com`).
|
|
Copy the files out of `/etc/letsencrypt/live/...` (they are symlinks into `archive/`) to a folder, and copy them
|
|
again after each renewal. The container runs as uid 1000, so the key must be readable by it
|
|
(`chown 1000 privkey.pem && chmod 600 privkey.pem`).
|
|
2. Mount the folder with a `docker-compose.override.yml` next to `docker-compose.yml` (compose reads it automatically):
|
|
```yaml
|
|
services:
|
|
electrumx:
|
|
volumes:
|
|
- /path/to/certs:/certs:ro
|
|
```
|
|
3. In `.env`:
|
|
```ini
|
|
SSL_CERTFILE=/certs/fullchain.pem
|
|
SSL_KEYFILE=/certs/privkey.pem
|
|
```
|
|
4. `docker compose up -d`. After a renewal, `docker compose restart electrumx` loads the new files.
|
|
|
|
If the files cannot be read, the container stops with a message that says so (see `docker compose logs electrumx`).
|
|
|
|
### Check it
|
|
|
|
```bash
|
|
echo | openssl s_client -connect 127.0.0.1:50002 2>/dev/null | openssl x509 -noout -subject -enddate -fingerprint -sha256
|
|
printf '{"id":1,"method":"server.version","params":["test","1.4"]}\n' | openssl s_client -quiet -connect 127.0.0.1:50002
|
|
```
|
|
|
|
The fingerprint must be the one printed in the log. Open 50002 as in "Ports" for a public server.
|
|
|
|
## Announcing a public server
|
|
|
|
`REPORT_SERVICES` in `.env` is the address other servers and clients are told to use. Set it only for a server that is
|
|
reachable from outside, with its public host name (not an IP on a private network, not `localhost`):
|
|
|
|
```ini
|
|
REPORT_SERVICES=tcp://electrum.example.com:50001,ssl://electrum.example.com:50002
|
|
```
|
|
|
|
Peer discovery is on by default (`PEER_DISCOVERY`, `PEER_ANNOUNCE`). `lib/coins.py` has no known Palladium servers yet
|
|
(`PEERS = []` in the `Palladium` class), so a new server has nobody to learn the network from. Add the
|
|
servers you know there (format: `'host s t'`, for example `'electrum.example.com s50002 t50001'`) and rebuild, or
|
|
keep the server private with `PEER_DISCOVERY=off` / `PEER_DISCOVERY=self`.
|
|
|
|
## Donation address
|
|
|
|
`DONATION_ADDRESS` in `.env` sets the address the server reports to its clients. It is optional and empty by default
|
|
(no address).
|
|
|
|
```ini
|
|
DONATION_ADDRESS=plm1q...your address...
|
|
```
|
|
|
|
- Clients get it with the Electrum protocol call `server.donation_address`; wallets that support it show it to the user.
|
|
- A banner file (`BANNER_FILE`, see `docs/environment.rst`) can contain the placeholder `$DONATION_ADDRESS`, which is
|
|
replaced with this address.
|
|
- The server does **not** check the value, it only passes it on. Check it carefully (for example send a small test
|
|
amount) and use an address you are happy to make public, since it is sent to everyone who connects.
|
|
- After changing it, apply it with `docker compose up -d` (the container is recreated with the new value).
|
|
|
|
## Architectures
|
|
|
|
amd64 and arm64 (for example a Raspberry Pi 5) are both supported: the Docker image builds natively on either.
|
|
Without Docker, the DB engine bindings are compiled from source, so install the development headers first:
|
|
`libleveldb-dev` for LevelDB (`plyvel`), or RocksDB >= 8.0 (`librocksdb-dev`) for RocksDB (`rocksdb-ng`).
|
|
Debian 12 ships RocksDB 7.8.3, which is too old: use Debian 13 (or the Docker image) for RocksDB.
|
|
|
|
## Data directory and first start
|
|
|
|
Create the data directory as the user that runs Docker, before the first start (otherwise Docker creates it as root and
|
|
the container cannot write; the log then says `ERROR: ... is not writable`):
|
|
|
|
```bash
|
|
mkdir -p ~/.plm-electrumx/db ~/.plm-electrumx/ssl
|
|
```
|
|
|
|
The container runs as uid/gid 1000 (the first user of most hosts). For another user set `PLM_UID` / `PLM_GID` in `.env`.
|
|
To keep the data elsewhere, set `ELECTRUMX_DATADIR` in `.env`.
|
|
|
|
- On the first start ElectrumX builds its database from the node, from genesis. With the chain at about 542,000 blocks
|
|
it took roughly a minute on a development machine, ending at about 230 MB of database and 0.5 GB of RAM.
|
|
The database grows with the chain.
|
|
- `CACHE_MB` in `.env` is the memory used for caching during the sync (default in `docker-compose.yml`: 1200).
|
|
- The data directory is `~/.plm-electrumx` (`ELECTRUMX_DATADIR`): `db/` is the database, `ssl/` the certificate.
|
|
The database is derived from the node, so it can be rebuilt at any time: stop the container, delete `db/` and start
|
|
again (it syncs again). A backup of it is optional; keep a backup of `ssl/`.
|
|
- Disk: the node needs `txindex`, so budget for the node's own data too (see the node's README).
|
|
|
|
## Operation
|
|
|
|
```bash
|
|
docker compose up -d --build # start / update after pulling changes
|
|
docker compose logs -f electrumx # follow the log
|
|
docker compose ps
|
|
docker compose stop # clean shutdown (up to 2 minutes to flush the db)
|
|
```
|
|
|
|
Is it up and synced? The log should end with `synced to height <N>` and the same height as the node
|
|
(`docker compose exec palladiumd palladium-cli getblockcount` in the node's compose project).
|
|
From the host, a protocol check:
|
|
|
|
```bash
|
|
printf '{"id":1,"method":"server.version","params":["test","1.4"]}\n' | nc -q1 127.0.0.1 50001
|
|
```
|
|
|
|
From another machine, `nc -zv <public IP> 50001` must succeed if the server is meant to be public, and
|
|
`nc -zv <public IP> 2332` must fail.
|
|
|
|
## Troubleshooting
|
|
|
|
| Symptom | Cause |
|
|
| --- | --- |
|
|
| `network palladium declared as external, but could not be found` | the node is not running (or its compose file was not started yet) |
|
|
| log: connection refused / cannot connect to `palladiumd:2332` | the node is stopped or still starting; check that the node container is running |
|
|
| log: `401`/authorization errors | wrong `PLM_RPC_USER` / `PLM_RPC_PASSWORD` in `.env`; the credentials must be the ones in the node's `palladium.conf` |
|
|
| errors mentioning `txindex` | `txindex=1` is missing in `palladium.conf` (an index added later is built while the node runs, which takes time) |
|
|
| `blockchain.outpoint.subscribe` or `blockchain.transaction.broadcast_package` return an error | expected: Palladium Core does not have the RPCs they need (`gettxspendingprevout`, `submitpackage`) |
|
|
| a client is disconnected, log says `disconnected whilst throttled` or `closing session over res usage` | expected: the server limits the cost of the requests of each client and IP (for example listing the UTXOs of an address with thousands of outputs); raise `COST_SOFT_LIMIT` / `COST_HARD_LIMIT` for your own trusted clients (see `docs/environment.rst`) |
|
|
| the server stays at an old height | the node is not synced; ElectrumX only follows the node |
|
|
| clients cannot connect from outside | port not published/forwarded, firewall, or `SERVICES` does not list that protocol |
|
|
| container stops at once, log says `ERROR: ... is not writable` | the data directory belongs to another user (or to root, if Docker created it): `chown -R $(id -u):$(id -g) ~/.plm-electrumx`, or set `PLM_UID`/`PLM_GID` |
|
|
| container restarts at once, log says `ERROR: ... SSL_CERTFILE` | your own certificate is not mounted or not readable by uid 1000, only one of `SSL_CERTFILE`/`SSL_KEYFILE` is set, or only one of the generated files exists in `~/.plm-electrumx/ssl` |
|
|
| a wallet refuses the server after it worked | the certificate changed (for example `ssl/` was deleted); the wallet saved the old one |
|