Bitcoin Forum
February 17, 2026, 11:50:50 PM *
News: Community awards 2025
 
   Home   Help Search Login Register More  
Pages: [1]
  Print  
Author Topic: DireWolfM14's Electrum SPV Server  (Read 131 times)
This is a self-moderated topic. If you do not want to be moderated by the person who started this topic, create a new topic.
DireWolfM14 (OP)
Copper Member
Legendary
*
Offline Offline

Activity: 2786
Merit: 5436



View Profile WWW
February 14, 2026, 06:20:01 PM
Last edit: February 14, 2026, 07:06:04 PM by DireWolfM14
Merited by DaveF (12), NeuroticFish (7), hosemary (4), ABCbits (3), Mia Chloe (2), philipma1957 (1), nc50lc (1), Charles-Tim (1), PX-Z (1)
 #1

Inspired by LoyceV's adventure, I decided to publish my own Electrum SPV server for the community to use.

Without further ado:

Electrum SPV server:
Clearnet: electrum.direwolfm14.com
Tor: xotqmhnei2wy7fk423tekp62ilcxpawnf4aiqmnkfhuutfkimgpqk5qd.onion

Bitcoin Core relay:
Clearnet: bitcoin.direwolfm14.com:8333
Tor: ezfbd7e5t6srgqfbjhqxpxqyg3x557rakkr7z5mhfqu7z6cuym7nmfyd.onion:8333



Minimum Tx fees set to 0.000001BTC per vByte.
No TxId or IP address logging.



Client side configuration:

SSL Clearnet:
Code:
electrum.direwolfm14.com:50002:s

SSL Tor:
Code:
xotqmhnei2wy7fk423tekp62ilcxpawnf4aiqmnkfhuutfkimgpqk5qd.onion:50002:s

TCP Clearnet:
Code:
electrum.direwolfm14.com:50001:t

TCP Tor:
Code:
xotqmhnei2wy7fk423tekp62ilcxpawnf4aiqmnkfhuutfkimgpqk5qd.onion:50001:t



Code:
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA256

DireWolfM14's Electrum SPV Server

Electrum SPV server:
Clearnet: electrum.direwolfm14.com
Tor: xotqmhnei2wy7fk423tekp62ilcxpawnf4aiqmnkfhuutfkimgpqk5qd.onion

Bitcoin Core relay:
Clearnet: bitcoin.direwolfm14.com:8333
Tor: ezfbd7e5t6srgqfbjhqxpxqyg3x557rakkr7z5mhfqu7z6cuym7nmfyd.onion:8333
-----BEGIN PGP SIGNATURE-----

iQJPBAEBCAA5FiEE5uCk8qpATL256qefg7ozyN3KpUcFAmmQumUbFIAAAAAABAAO
bWFudTIsMi41KzEuMTEsMiwxAAoJEIO6M8jdyqVHQTQP/jdcoAq20iD07HHyoiRZ
jyYyCy0gy2z2PLNCLP1ALHUTQNy+fog5AdZo8OnmItMm/WhSL0bJSoOJvWHy7sYC
aDjUNF+ij6DBJl0KAIDvF1ITaOitFqZm1kBfS+YY3yHEb6DcWHZaj9MQu0NI8myT
vHF+y7qvMEePjqzFvxH2qTbx+G3KPGrkbaX4eeUcz4GTqhqaI2xmHmxDbVfNoNl7
VIbJ5JyxiUb2W14D40kYkAxXqnL2GujWlEDKnEvQC9I19EGT1zklp4iessRMwnwc
sN78bEdQDPbih0Hh1rAXG8cdMBJZfMOxx5Gb7UHsMeItQMLkO9T/roZb7QOUpf9m
HvUWecWrdS73iKhPFRpV1iUr5sEh8NY7Vjgoi1fwqG0QcYuaJdkPJUkG/N9+2EVd
Yp5NVPjdJ/GtpwttvUZzPdWgTAId8afBhLdyw/vUS3baJHfQl5W6CnYcExQwZAz4
LdjcN3Kbl9QVmNDUXJum/VP+/fpaiN0V+b84UPVLppmK4nwf05PiOA+aMmvwD6aj
dGmfqMlIL8svV96NetTyLrYgtmY5eaBCaWfEmnzElCMIZQusuZFX2OeOEYSedUVi
oOWozP4aVdIVBhELQnJeULKP7VHIg8eC4dKHGRTE7BWUGjdtOWV71+pSj9IGgiC4
PWKkf+NzsV89Qqp6OSmBGy11
=Ok7F
-----END PGP SIGNATURE-----
GPG key staked here.
Self-moderated thread - Please remain on topic.  Discussion, suggestions, and pertinent comments are welcome.  No spam.

 
 b1exch.io 
  ETH      DAI   
  BTC      LTC   
  USDT     XMR    
.███████████▄▀▄▀
█████████▄█▄▀
███████████
███████▄█▀
█▀█
▄▄▀░░██▄▄
▄▀██▄▀█████▄
██▄▀░▄██████
███████░█████
█░████░█████████
█░█░█░████░█████
█░█░█░██░█████
▀▀▀▄█▄████▀▀▀
DireWolfM14 (OP)
Copper Member
Legendary
*
Offline Offline

Activity: 2786
Merit: 5436



View Profile WWW
February 14, 2026, 06:43:42 PM
Last edit: February 14, 2026, 06:59:39 PM by DireWolfM14
 #2

I've been meaning to undertake a project like this for quite some time, but was shy about linking a publicly-accessible server to my home IP address.  Through the discussion in LoyceV's adventure thread I found an affordable solution; a VPS with plenty of horsepower and diskspace.

I've been running my own personal SPV server and block explorer on private hardware for almost 6 years now, so I have quite a bit of experience running a full node.  However, this is the first time I've used Fulcrum for the SPV server interface.  Previously I've used ElectrumX on my rack server and Romanz Electrs on my Dell Optiplex mini nodebox that I like to run as a backup incase the server was being finicky or (more likely,) I broke something while tinkering with it.

I found Fulcrum pleasantly simple to set up and work with, and it's noticeably faster to synch my larger wallet files than ElectrumX.  

Here I'll share my configuration files incase anyone else cares to take on a similar project:

bitcoin.conf
Code:
server=1
listen=1
txindex=1
dbcache=16384
maxmempool=16384
prune=0
peerbloomfilters=1
mempoolfullrbf=1
datadir=/var/lib/bitcoind
onion=127.0.0.1:9050
discover=1
proxyrandomize=1
externalip=ezfbd7e5t6srgqfbjhqxpxqyg3x557rakkr7z5mhfqu7z6cuym7nmfyd.onion
zmqpubrawblock=tcp://127.0.0.1:28332
zmqpubrawtx=tcp://127.0.0.1:28333
rpcauth=<rpc-username>:<rpc-authentication-hash>
[main]

[test]

[regtest]

fulcrum.conf
Code:
datadir = /var/lib/fulcrum
bitcoind = 127.0.0.1:8332
rpcuser = <rpc-username>
rpcpassword = <rpc-password>
admin = 127.0.0.1:8000
tcp = 0.0.0.0:50001
ssl = 0.0.0.0:50002
cert = /opt/fulcrum/fulcrum_ssl_cert.pem
key = /opt/fulcrum/fulcrum_ssl_key.pem
debug = false
quiet = true
anon_logs = true
hostname = electrum.direwolfm14.com
donation = bc1qgl9rygq2us04vx5262660unrt4jfuxe25h0ah8
banner = /opt/fulcrum/banner.txt
public_tcp_port = 50001
public_ssl_port = 50002
peering = true
announce = true
tor_hostname=xotqmhnei2wy7fk423tekp62ilcxpawnf4aiqmnkfhuutfkimgpqk5qd.onion
tor_tcp_port = 50001
tor_ssl_port = 50002

 
 b1exch.io 
  ETH      DAI   
  BTC      LTC   
  USDT     XMR    
.███████████▄▀▄▀
█████████▄█▄▀
███████████
███████▄█▀
█▀█
▄▄▀░░██▄▄
▄▀██▄▀█████▄
██▄▀░▄██████
███████░█████
█░████░█████████
█░█░█░████░█████
█░█░█░██░█████
▀▀▀▄█▄████▀▀▀
DireWolfM14 (OP)
Copper Member
Legendary
*
Offline Offline

Activity: 2786
Merit: 5436



View Profile WWW
February 14, 2026, 06:45:13 PM
 #3

Reserved.

 
 b1exch.io 
  ETH      DAI   
  BTC      LTC   
  USDT     XMR    
.███████████▄▀▄▀
█████████▄█▄▀
███████████
███████▄█▀
█▀█
▄▄▀░░██▄▄
▄▀██▄▀█████▄
██▄▀░▄██████
███████░█████
█░████░█████████
█░█░█░████░█████
█░█░█░██░█████
▀▀▀▄█▄████▀▀▀
nc50lc
Legendary
*
Offline Offline

Activity: 3052
Merit: 8418


Self-proclaimed Genius


View Profile
February 16, 2026, 04:58:34 AM
Merited by LoyceV (4), ABCbits (1), DireWolfM14 (1)
 #4

I tested it and it works great on my end.

Tor connection, over SSL.
Electrum:

Sparrow:


Sub-1sat/vB transactions can be created (I tested Sparrow to visually check):

In Sparrow, that "0.1" sat/vB fee on the slider wont appear if the server doesn't support sub-1sat/vB fee rate,
It does on this server.

-snip- a VPS with plenty of horsepower and diskspace.
Its RAM must be capable of utilizing more transaction hash cache with: txhash_cache = 2000 instead of the small default of 128MiB.

███████████████████████████
███████▄████████████▄██████
████████▄████████▄████████
███▀█████▀▄███▄▀█████▀███
█████▀█▀▄██▀▀▀██▄▀█▀█████
███████▄███████████▄███████
███████████████████████████
███████▀███████████▀███████
████▄██▄▀██▄▄▄██▀▄██▄████
████▄████▄▀███▀▄████▄████
██▄███▀▀█▀██████▀█▀███▄███
██▀█▀████████████████▀█▀███
███████████████████████████
.
.Duelbits PREDICT..
█████████████████████████
█████████████████████████
███████████▀▀░░░░▀▀██████
██████████░░▄████▄░░████
█████████░░████████░░████
█████████░░████████░░████
█████████▄▀██████▀▄████
████████▀▀░░░▀▀▀▀░░▄█████
██████▀░░░░██▄▄▄▄████████
████▀░░░░▄███████████████
█████▄▄█████████████████
█████████████████████████
█████████████████████████
.
.WHERE EVERYTHING IS A MARKET..
█████
██
██







██
██
██████
Will Bitcoin hit $200,000
before January 1st 2027?

    No @1.15         Yes @6.00    
█████
██
██







██
██
██████

  CHECK MORE > 
DireWolfM14 (OP)
Copper Member
Legendary
*
Offline Offline

Activity: 2786
Merit: 5436



View Profile WWW
February 16, 2026, 05:36:17 PM
 #5

Its RAM must be capable of utilizing more transaction hash cache with: txhash_cache = 2000 instead of the small default of 128MiB.

Awesome suggestion, thank you.  I just added that to the config file and restarted.

And thanks for checking it out and providing your feedback.  

I'm rather surprised by how much usage the server is getting.  Yesterday I checked and it showed as many as 270 clients connected at once, and over 27k life time.  Since restarting it a few minutes ago it's already showing this:

Code:
    "clients_connected": 215,
    "clients_connected_max_lifetime": 221,
    "clients_connected_total_lifetime": 522,

Here's the entirety of the getinfo results.  If you see anything else I can change that could improve performance, please let me know.

Code:
{
    "bitcoind": "127.0.0.1:8332",
    "bitcoind_info": {
        "hasDSProofRPC": false,
        "hasEstimateSmartFee": true,
        "hasSubmitPackageRPC": true,
        "isBU": false,
        "isBchd": false,
        "isCore": true,
        "isFlowee": false,
        "isLTC": false,
        "isTwoArgEstimateSmartFee": true,
        "isZeroArgEstimateFee": false,
        "lacksGetZmqNotifications": false,
        "relayfee": 1e-06,
        "sendRawTransactionRequiresMaxBurnAmount": true,
        "subversion": "/Satoshi:30.2.0/",
        "version": "0.30.2",
        "warnings": "",
        "zmqNotifications": [
            [
                "rawblock",
                "tcp://127.0.0.1:28332"
            ],
            [
                "rawtx",
                "tcp://127.0.0.1:28333"
            ]
        ]
    },
    "chain": "main",
    "clients_connected": 215,
    "clients_connected_max_lifetime": 221,
    "clients_connected_total_lifetime": 522,
    "coin": "BTC",
    "config": {
        "admin": [
            "127.0.0.1:8000"
        ],
        "anon_logs": true,
        "banner": "/opt/fulcrum/banner.txt",
        "bitcoind": "127.0.0.1:8332",
        "bitcoind-tls": false,
        "bitcoind_clients": 3,
        "bitcoind_throttle": [
            50,
            20,
            5
        ],
        "bitcoind_timeout": 30000,
        "cert": "/opt/fulcrum/fulcrum_ssl_cert.pem",
        "checkdb": 0,
        "datadir": "/var/lib/fulcrum",
        "db_keep_log_file_num": 5,
        "db_max_open_files": 1000,
        "db_mem": 2048,
        "db_use_fsync": false,
        "debug": false,
        "donation": "bc1qgl9rygq2us04vx5262660unrt4jfuxe25h0ah8",
        "hasIPv6 listener": false,
        "hostname": "electrum.direwolfm14.com",
        "key": "/opt/fulcrum/fulcrum_ssl_key.pem",
        "max_batch": 345,
        "max_buffer": 8000000,
        "max_clients_per_ip": 12,
        "max_history": 125000,
        "max_pending_connections": 60,
        "max_reorg": 100,
        "max_subs": 10000000,
        "max_subs_per_ip": 75000,
        "peering": true,
        "peering_announce_self": true,
        "peering_enforce_unique_ip": true,
        "pidfile": "",
        "polltime": 2,
        "public_ssl_port": 50002,
        "public_tcp_port": 50001,
        "public_ws_port": null,
        "public_wss_port": null,
        "rpa": "auto (enabled for BCH only)",
        "rpa_history_blocks_limit": 60,
        "rpa_max_history": 125000,
        "rpa_prefix_bits_min": 8,
        "rpa_start_height": -1,
        "rpcpassword": "<hidden>",
        "rpcuser": "<hidden>",
        "simdjson": true,
        "ssl": [
            "0.0.0.0:50002"
        ],
        "stats": [],
        "subnets_to_exclude_from_per_ip_limits": [
            "127.0.0.1/32",
            "::1/128"
        ],
        "tcp": [
            "0.0.0.0:50001"
        ],
        "tls-disallow-deprecated": false,
        "tor_hostname": "xotqmhnei2wy7fk423tekp62ilcxpawnf4aiqmnkfhuutfkimgpqk5qd.onion",
        "tor_pass": null,
        "tor_proxy": "127.0.0.1:9050",
        "tor_ssl_port": 50002,
        "tor_tcp_port": 50001,
        "tor_user": null,
        "tor_ws_port": null,
        "tor_wss_port": null,
        "trace": false,
        "ts-format": "localtime",
        "txhash_cache": 2000,
        "upnp": false,
        "worker_threads": 8,
        "workqueue": 15000,
        "ws": [],
        "wss": [],
        "zmq_allow_hashtx": false
    },
    "genesis_hash": "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f",
    "height": 936943,
    "jemalloc": {
        "stats": {
            "active": 2494541824,
            "allocated": 2311916984,
            "background_thread": {
                "num_runs": 0,
                "num_threads": 0,
                "run_interval": 0
            },
            "mapped": 2660634624,
            "metadata": 60390048,
            "metadata_thp": 0,
            "resident": 2592108544,
            "retained": 900329472,
            "zero_reallocs": 0
        },
        "version": "5.3.0-0-g54eaed1d8b56b1aa528be3bdd1877e59c56fa90c"
    },
    "memory_usage": {
        "physical_kb": 2619980,
        "virtual_kb": 3823480
    },
    "mempool": {
        "addresses": 16628,
        "avg_fee_sats_B": 0.27,
        "avg_fee_sats_vB": 0.55,
        "size_bytes": 12718216,
        "size_vbytes": 6202626,
        "txs": 14674
    },
    "peers": 234,
    "pid": 341291,
    "storage_stats": {
        "DB Shared Block Cache": {
            "capacity": 2147483648,
            "usage": 2147373200
        },
        "DB Shared Write Buffer Manager": {
            "buffer size": 1073741824,
            "enabled": true,
            "is costed to cache": true,
            "memory usage": 9459712
        },
        "DB Stats": {
            "fulc2_db": {
                "rocksdb.cur-size-all-mem-tables": "2048",
                "rocksdb.estimate-table-readers-mem": "0"
            }
        },
        "RPA Index Info": {
            "firstHeight": -1,
            "lastHeight": -1,
            "nBytesRead": 0,
            "nBytesWritten": 0,
            "nDeletions": 0,
            "nReads": 0,
            "nWrites": 0,
            "needsFullCheck": -1
        },
        "caches": {
            "LRU Cache: Block Height -> TxHashes": {
                "Size bytes": 0,
                "max bytes": 1000000000,
                "nBlocks": 0,
                "~hits": 0,
                "~misses": 0
            },
            "LRU Cache: TxNum -> TxHash": {
                "Size bytes": 29250,
                "max bytes": 1000000000,
                "nItems": 250,
                "~hits": 896,
                "~misses": 252
            },
            "merkleHeaders_Size": 915,
            "merkleHeaders_SizeBytes": 51240
        },
        "merge calls (headers)": 6291,
        "merge calls (scripthash_history)": 42,
        "merge calls (txhash2txnum)": 1,
        "merge calls (txnum2txhash)": 258
    },
    "subscriptions": 120,
    "thread_pool": {
        "extant jobs": 0,
        "extant jobs (max lifetime)": 29,
        "extant limit": 15000,
        "job count (lifetime)": 7147,
        "job queue overflows (lifetime)": 0,
        "thread count (max)": 8
    },
    "txs_sent": 0,
    "txs_sent_bytes": 0,
    "uptime": "11.8 mins",
    "utxoset": {
        "size": 164297102,
        "size_MB": 13800.96
    },
    "version": "Fulcrum 2.1.0"
}

 
 b1exch.io 
  ETH      DAI   
  BTC      LTC   
  USDT     XMR    
.███████████▄▀▄▀
█████████▄█▄▀
███████████
███████▄█▀
█▀█
▄▄▀░░██▄▄
▄▀██▄▀█████▄
██▄▀░▄██████
███████░█████
█░████░█████████
█░█░█░████░█████
█░█░█░██░█████
▀▀▀▄█▄████▀▀▀
LoyceV
Legendary
*
Offline Offline

Activity: 3948
Merit: 21218


Thick-Skinned Gang Leader and Golden Feather 2021


View Profile WWW
February 16, 2026, 05:51:26 PM
Merited by DireWolfM14 (1), stwenhao (1)
 #6

I'm rather surprised by how much usage the server is getting.  Yesterday I checked and it showed as many as 270 clients connected at once, and over 27k life time.
This surprised me too ("uptime": "15.84 days"):
Code:
"clients_connected": 281,
    "clients_connected_max_lifetime": 387,
    "clients_connected_total_lifetime": 959921,
It makes me wonder what "connected" means: my own Electrum client shows 10 nodes under "Connected nodes", so I guess those 10 all count me as "connected". It also has a much larger list under "Other known servers", including yours. I didn't manually add yours, it just appeared.
The "lifetime" amount must be counting the same couple of hundred clients many times, as it shouldn't log who was counted before.

I now assume this is a better estimate of actual uses:
Code:
    "txs_sent": 1126,
    "txs_sent_bytes": 757277,

¡uʍop ǝpᴉsdn pɐǝɥ ɹnoʎ ɥʇᴉʍ ʎuunɟ ʞool no⅄
stwenhao
Hero Member
*****
Offline Offline

Activity: 619
Merit: 1560


View Profile
February 16, 2026, 08:01:27 PM
 #7

Quote
It makes me wonder what "connected" means: my own Electrum client shows 10 nodes under "Connected nodes"
There are incoming and outgoing connections. By default, Bitcoin Core uses only outgoing connections, when you have a typical client at home. But if you have any server, then other people can also connect directly to your IP, and then you have incoming connections.

For example:
Code:
$ bitcoin-cli getpeerinfo | grep connection_type | sort | uniq -c
      2     "connection_type": "block-relay-only",
    114     "connection_type": "inbound",
      8     "connection_type": "outbound-full-relay",

Proof of Work puzzle in mainnet, testnet4 and signet.
LoyceV
Legendary
*
Offline Offline

Activity: 3948
Merit: 21218


Thick-Skinned Gang Leader and Golden Feather 2021


View Profile WWW
Today at 06:30:26 AM
Merited by stwenhao (1)
 #8

Quote
It makes me wonder what "connected" means: my own Electrum client shows 10 nodes under "Connected nodes"
There are incoming and outgoing connections. By default, Bitcoin Core uses only outgoing connections, when you have a typical client at home. But if you have any server, then other people can also connect directly to your IP, and then you have incoming connections.
Are you saying Fulcrum counts Bitcoin Core's connections as it's own?

¡uʍop ǝpᴉsdn pɐǝɥ ɹnoʎ ɥʇᴉʍ ʎuunɟ ʞool no⅄
stwenhao
Hero Member
*****
Offline Offline

Activity: 619
Merit: 1560


View Profile
Today at 07:21:45 AM
Merited by DireWolfM14 (1)
 #9

Quote
Are you saying Fulcrum counts Bitcoin Core's connections as it's own?
Well, let's see: https://github.com/cculianu/Fulcrum/blob/master/src/Servers.cpp#L3101
Code:
void AdminServer::rpc_getinfo(Client *c, const RPC::BatchId batchId, const RPC::Message &m)
{
    QVariantMap res;
    res["bitcoind"] = QString("%1:%2").arg(options->bdRPCInfo.hostPort.first, QString::number(options->bdRPCInfo.hostPort.second));
    res["bitcoind_info"] = bitcoindmgr->getBitcoinDInfo().toVariantMap();
    {
        const auto opt = storage->latestHeight();
        res["height"] = opt.has_value() ? *opt : QVariant();
    }
    res["chain"] = storage->getChain();
    res["coin"] = storage->getCoin();
    res["genesis_hash"] = Util::ToHexFast(storage->genesisHash());
    res["pid"] = QCoreApplication::applicationPid();
    res["clients_connected"] = qulonglong(Client::numClients.load());
As you can see, there is "Client::numClients.load()". If we dig deeper, we will get this: https://github.com/cculianu/Fulcrum/blob/master/src/Servers.h#L703
Code:
/// Encapsulates an Electron Cash (Electrum) Client
/// These run and live in 'Server' instance thread
/// Note that their parent QObject is the socket!
/// (grandparent is Server) .. so they will be destroyed
/// when the server goes away or the socket is deleted.
class Client : public RPC::ElectrumConnection
{
    //...
    static std::atomic_size_t numClients, numClientsMax, numClientsCtr; // number of connected clients: current, max lifetime, accumulated counter
For now, I am not sure, but tracing how "numClients" is used, should give us that answer. But I guess if you have some Bitcoin node, and you want to get some information out of it, then you have to behave like one, so maybe there is no distinction between Fulcrum connection and Bitcoin Core connection. Because in the end, if you have some peer, and if it can serve you the things you want, then it doesn't matter, what is the exact implementation, which is baked into all of that.

Or, even if Fulcrum and Bitcoin Core connections are separated, then still: the P2P implementation can be done in a similar way.

Proof of Work puzzle in mainnet, testnet4 and signet.
Pages: [1]
  Print  
 
Jump to:  

Powered by MySQL Powered by PHP Powered by SMF 1.1.19 | SMF © 2006-2009, Simple Machines Valid XHTML 1.0! Valid CSS!