fulcrum: add module

This commit is contained in:
Erik Arvstedt 2022-07-04 12:15:44 +02:00
parent edd8bd311c
commit 7d7f2df006
No known key found for this signature in database
GPG Key ID: 33312B944DD97846
11 changed files with 190 additions and 7 deletions

View File

@ -85,6 +85,7 @@ NixOS modules ([src](modules/modules.nix))
* [Ride The Lightning](https://github.com/Ride-The-Lightning/RTL): web interface for `lnd` and `clightning` * [Ride The Lightning](https://github.com/Ride-The-Lightning/RTL): web interface for `lnd` and `clightning`
* [spark-wallet](https://github.com/shesek/spark-wallet) * [spark-wallet](https://github.com/shesek/spark-wallet)
* [electrs](https://github.com/romanz/electrs) * [electrs](https://github.com/romanz/electrs)
* [fulcrum](https://github.com/cculianu/Fulcrum) (see [the module](modules/fulcrum.nix) for a comparison to electrs)
* [btcpayserver](https://github.com/btcpayserver/btcpayserver) * [btcpayserver](https://github.com/btcpayserver/btcpayserver)
* [liquid](https://github.com/elementsproject/elements) * [liquid](https://github.com/elementsproject/elements)
* [JoinMarket](https://github.com/joinmarket-org/joinmarket-clientserver) * [JoinMarket](https://github.com/joinmarket-org/joinmarket-clientserver)

View File

@ -5,7 +5,7 @@ Hardware requirements
* Disk space: 500 GB (400GB for Bitcoin blockchain + some room) for an unpruned * Disk space: 500 GB (400GB for Bitcoin blockchain + some room) for an unpruned
instance of Bitcoin Core. instance of Bitcoin Core.
* This can be significantly lowered by enabling pruning. * This can be significantly lowered by enabling pruning.
Note: Pruning is not supported by `electrs`. Note: Pruning is not supported by `electrs` and `fulcrum`.
Tested low-end hardware includes: Tested low-end hardware includes:
- [Raspberry Pi 4](https://www.raspberrypi.org/products/raspberry-pi-4-model-b/) - [Raspberry Pi 4](https://www.raspberrypi.org/products/raspberry-pi-4-model-b/)

View File

@ -122,9 +122,20 @@
# services.spark-wallet.enable = true; # services.spark-wallet.enable = true;
### ELECTRS ### ELECTRS
# Set this to enable electrs, an efficient Electrum server implemented in Rust. # Set this to enable electrs, an Electrum server implemented in Rust.
# services.electrs.enable = true; # services.electrs.enable = true;
### FULCRUM
# Set this to enable fulcrum, an Electrum server implemented in C++.
#
# Compared to electrs, fulcrum has higher storage demands but
# can serve arbitrary address queries instantly.
#
# Before enabling fulcrum, and for more info on storage demands,
# see the description of option `enable` in ../modules/fulcrum.nix
#
# services.fulcrum.enable = true;
### BTCPayServer ### BTCPayServer
# Set this to enable BTCPayServer, a self-hosted, open-source # Set this to enable BTCPayServer, a self-hosted, open-source
# cryptocurrency payment processor. # cryptocurrency payment processor.

139
modules/fulcrum.nix Normal file
View File

@ -0,0 +1,139 @@
{ config, lib, pkgs, ... }:
with lib;
let
options.services.fulcrum = {
enable = mkOption {
type = types.bool;
default = false;
description = ''
Enable fulcrum, an Electrum server implemented in C++.
Compared to electrs, fulcrum has a 3x larger database size but
can serve arbitrary address queries instantly.
fulcrum also enables `txindex` in bitcoind (this is a requirement),
which increases the bitcoind datadir size by 8% of the `blocks` size.
This module disables peering (a distributed list of electrum servers that can
be queried by clients), but you can manually enable it via option
`extraConfig`.
'';
};
address = mkOption {
type = types.str;
default = "127.0.0.1";
description = "Address to listen for RPC connections.";
};
port = mkOption {
type = types.port;
default = 50001;
description = "Port to listen for RPC connections.";
};
dataDir = mkOption {
type = types.path;
default = "/var/lib/fulcrum";
description = "The data directory for fulcrum.";
};
extraConfig = mkOption {
type = types.lines;
default = "";
example = ''
peering = true
'';
description = ''
Extra lines appended to the configuration file.
See all available options at
https://github.com/cculianu/Fulcrum/blob/master/doc/fulcrum-example-config.conf
'';
};
user = mkOption {
type = types.str;
default = "fulcrum";
description = "The user as which to run fulcrum.";
};
group = mkOption {
type = types.str;
default = cfg.user;
description = "The group as which to run fulcrum.";
};
tor.enforce = nbLib.tor.enforce;
};
cfg = config.services.fulcrum;
nbLib = config.nix-bitcoin.lib;
secretsDir = config.nix-bitcoin.secretsDir;
bitcoind = config.services.bitcoind;
configFile = builtins.toFile "fulcrum.conf" ''
datadir = ${cfg.dataDir}
tcp = ${cfg.address}:${toString cfg.port}
bitcoind = ${nbLib.addressWithPort bitcoind.rpc.address bitcoind.rpc.port}
rpcuser = ${bitcoind.rpc.users.public.name}
# Disable logging timestamps
ts-format = none
peering = false
${cfg.extraConfig}
'';
in {
inherit options;
config = mkIf cfg.enable {
assertions = [
{ assertion = bitcoind.prune == 0;
message = "Fulcrum does not support bitcoind pruning.";
}
{ assertion =
!(config.services ? electrs)
|| !config.services.electrs.enable
|| config.services.electrs.port != cfg.port;
message = ''
Fulcrum and Electrs can't both bind to TCP RPC port ${cfg.port}.
Change `services.electrs.port` or `services.fulcrum.port`
to a port other than ${cfg.port}.
'';
}
];
services.bitcoind = {
enable = true;
txindex = true;
};
systemd.tmpfiles.rules = [
"d '${cfg.dataDir}' 0770 ${cfg.user} ${cfg.group} - -"
];
systemd.services.fulcrum = {
wantedBy = [ "multi-user.target" ];
requires = [ "bitcoind.service" ];
after = [ "bitcoind.service" ];
preStart = ''
{
cat ${configFile}
echo "rpcpassword = $(cat ${secretsDir}/bitcoin-rpcpassword-public)"
} > '${cfg.dataDir}/fulcrum.conf'
'';
serviceConfig = nbLib.defaultHardening // {
ExecStart = "${config.nix-bitcoin.pkgs.fulcrum}/bin/Fulcrum '${cfg.dataDir}/fulcrum.conf'";
User = cfg.user;
Group = cfg.group;
Restart = "on-failure";
RestartSec = "10s";
ReadWritePaths = cfg.dataDir;
} // nbLib.allowedIPAddresses cfg.tor.enforce;
};
users.users.${cfg.user} = {
isSystemUser = true;
group = cfg.group;
extraGroups = [ "bitcoinrpc-public" ];
};
users.groups.${cfg.group} = {};
};
}

View File

@ -21,6 +21,7 @@
./lndconnect-onion.nix # Requires onion-addresses.nix ./lndconnect-onion.nix # Requires onion-addresses.nix
./rtl.nix ./rtl.nix
./electrs.nix ./electrs.nix
./fulcrum.nix
./liquid.nix ./liquid.nix
./btcpayserver.nix ./btcpayserver.nix
./joinmarket.nix ./joinmarket.nix

View File

@ -293,6 +293,10 @@ in {
clightning-rest = { clightning-rest = {
id = 30; id = 30;
}; };
fulcrum = {
id = 31;
connections = [ "bitcoind" ];
};
}; };
services.bitcoind = { services.bitcoind = {
@ -324,6 +328,8 @@ in {
services.electrs.address = netns.electrs.address; services.electrs.address = netns.electrs.address;
services.fulcrum.address = netns.fulcrum.address;
services.spark-wallet = { services.spark-wallet = {
address = netns.spark-wallet.address; address = netns.spark-wallet.address;
extraArgs = "--no-tls"; extraArgs = "--no-tls";

View File

@ -132,6 +132,7 @@ in {
''; '';
clightning-rest = mkInfo ""; clightning-rest = mkInfo "";
electrs = mkInfo ""; electrs = mkInfo "";
fulcrum = mkInfo "";
spark-wallet = mkInfo ""; spark-wallet = mkInfo "";
btcpayserver = mkInfo ""; btcpayserver = mkInfo "";
liquidd = mkInfo ""; liquidd = mkInfo "";

View File

@ -34,6 +34,7 @@ in {
# but we restrict them to Tor just to be safe. # but we restrict them to Tor just to be safe.
# #
electrs = defaultEnforceTor; electrs = defaultEnforceTor;
fulcrum = defaultEnforceTor;
nbxplorer = defaultEnforceTor; nbxplorer = defaultEnforceTor;
rtl = defaultEnforceTor; rtl = defaultEnforceTor;
joinmarket = defaultEnforceTor; joinmarket = defaultEnforceTor;
@ -46,6 +47,7 @@ in {
bitcoind.enable = defaultTrue; bitcoind.enable = defaultTrue;
liquidd.enable = defaultTrue; liquidd.enable = defaultTrue;
electrs.enable = defaultTrue; electrs.enable = defaultTrue;
fulcrum.enable = defaultTrue;
spark-wallet.enable = defaultTrue; spark-wallet.enable = defaultTrue;
joinmarket-ob-watcher.enable = defaultTrue; joinmarket-ob-watcher.enable = defaultTrue;
rtl.enable = defaultTrue; rtl.enable = defaultTrue;

View File

@ -14,6 +14,7 @@ pkgs: pkgsUnstable:
inherit (pkgsUnstable) inherit (pkgsUnstable)
btcpayserver btcpayserver
clightning clightning
fulcrum
hwi hwi
lightning-loop lightning-loop
lnd lnd

View File

@ -95,6 +95,9 @@ let
tests.electrs = cfg.electrs.enable; tests.electrs = cfg.electrs.enable;
services.fulcrum.port = 50002;
tests.fulcrum = cfg.fulcrum.enable;
tests.liquidd = cfg.liquidd.enable; tests.liquidd = cfg.liquidd.enable;
services.liquidd.extraConfig = mkIf config.test.noConnections "connect=0"; services.liquidd.extraConfig = mkIf config.test.noConnections "connect=0";
@ -182,6 +185,7 @@ let
services.lightning-pool.enable = true; services.lightning-pool.enable = true;
services.charge-lnd.enable = true; services.charge-lnd.enable = true;
services.electrs.enable = true; services.electrs.enable = true;
services.fulcrum.enable = true;
services.liquidd.enable = true; services.liquidd.enable = true;
services.btcpayserver.enable = true; services.btcpayserver.enable = true;
services.joinmarket.enable = true; services.joinmarket.enable = true;
@ -228,6 +232,7 @@ let
services.lightning-pool.enable = true; services.lightning-pool.enable = true;
services.charge-lnd.enable = true; services.charge-lnd.enable = true;
services.electrs.enable = true; services.electrs.enable = true;
services.fulcrum.enable = true;
services.btcpayserver.enable = true; services.btcpayserver.enable = true;
services.joinmarket.enable = true; services.joinmarket.enable = true;
}; };

View File

@ -110,6 +110,11 @@ def _():
log_has_string("electrs", "waiting for 0 blocks to download") log_has_string("electrs", "waiting for 0 blocks to download")
) )
@test("fulcrum")
def _():
assert_running("fulcrum")
machine.wait_until_succeeds(log_has_string("fulcrum", "started ok"))
# Impure: Stops electrs # Impure: Stops electrs
# Stop electrs from spamming the test log with 'waiting for 0 blocks to download' messages # Stop electrs from spamming the test log with 'waiting for 0 blocks to download' messages
@test("stop-electrs") @test("stop-electrs")
@ -383,33 +388,44 @@ def _():
else: else:
return False return False
def get_block_height(ip, port):
return (
"""echo '{"method": "blockchain.headers.subscribe", "id": 0}'"""
f" | nc {ip} {port} | head -1 | jq -M .result.height"
)
num_blocks = test_data["num_blocks"] num_blocks = test_data["num_blocks"]
if enabled("electrs"): if enabled("electrs"):
machine.wait_until_succeeds(log_has_string("electrs", "serving Electrum RPC")) machine.wait_until_succeeds(log_has_string("electrs", "serving Electrum RPC"))
get_block_height_cmd = ( assert_full_match(get_block_height(ip('electrs'), 50001), f"{num_blocks}\n")
"""echo '{"method": "blockchain.headers.subscribe", "id": 0, "params": []}'"""
f" | nc {ip('electrs')} 50001 | head -1 | jq -M .result.height" if enabled("fulcrum"):
) machine.wait_until_succeeds(log_has_string("fulcrum", "listening for connections"))
assert_full_match(get_block_height_cmd, f"{num_blocks}\n") assert_full_match(get_block_height(ip('fulcrum'), 50002), f"{num_blocks}\n")
if enabled("clightning"): if enabled("clightning"):
machine.wait_until_succeeds( machine.wait_until_succeeds(
f"[[ $(runuser -u operator -- lightning-cli getinfo | jq -M .blockheight) == {num_blocks} ]]" f"[[ $(runuser -u operator -- lightning-cli getinfo | jq -M .blockheight) == {num_blocks} ]]"
) )
if enabled("lnd"): if enabled("lnd"):
machine.wait_until_succeeds( machine.wait_until_succeeds(
f"[[ $(runuser -u operator -- lncli getinfo | jq -M .block_height) == {num_blocks} ]]" f"[[ $(runuser -u operator -- lncli getinfo | jq -M .block_height) == {num_blocks} ]]"
) )
if enabled("lightning-loop"): if enabled("lightning-loop"):
machine.wait_until_succeeds( machine.wait_until_succeeds(
log_has_string("lightning-loop", f"Starting event loop at height {num_blocks}") log_has_string("lightning-loop", f"Starting event loop at height {num_blocks}")
) )
succeed("runuser -u operator -- loop getparams") succeed("runuser -u operator -- loop getparams")
if enabled("lightning-pool"): if enabled("lightning-pool"):
machine.wait_until_succeeds( machine.wait_until_succeeds(
log_has_string("lightning-pool", "lnd is now fully synced to its chain backend") log_has_string("lightning-pool", "lnd is now fully synced to its chain backend")
) )
succeed("runuser -u operator -- pool orders list") succeed("runuser -u operator -- pool orders list")
if enabled("btcpayserver"): if enabled("btcpayserver"):
machine.wait_until_succeeds(log_has_string("nbxplorer", f"At height: {num_blocks}")) machine.wait_until_succeeds(log_has_string("nbxplorer", f"At height: {num_blocks}"))