diff --git a/.travis.yml b/.travis.yml index 1942ce7..6fb6f38 100644 --- a/.travis.yml +++ b/.travis.yml @@ -28,6 +28,7 @@ env: - PKG=electrs STABLE=1 - PKG=electrs STABLE=0 - PKG=liquid-swap STABLE=1 + - PKG=lightning-loop STABLE=0 - PKG=nixops19_09 STABLE=1 script: - printf '%s (%s)\n' "$NIX_PATH" "$VER" diff --git a/examples/configuration.nix b/examples/configuration.nix index da23ca2..f1030ff 100644 --- a/examples/configuration.nix +++ b/examples/configuration.nix @@ -129,6 +129,12 @@ # a network-level as much as possible. # nix-bitcoin.netns-isolation.enable = true; + ### lightning-loop + # Enable this module to use lightninglab's non-custodial off/on chain bridge. + # loopd (lightning-loop daemon) will be started automatically. Users can + # interact with off/on chain bridge using `loop in` and `loop out`. + # services.lightning-loop.enable = true; + # FIXME: Define your hostname. networking.hostName = "nix-bitcoin"; time.timeZone = "UTC"; diff --git a/modules/lightning-loop.nix b/modules/lightning-loop.nix new file mode 100644 index 0000000..0c84873 --- /dev/null +++ b/modules/lightning-loop.nix @@ -0,0 +1,72 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.lightning-loop; + inherit (config) nix-bitcoin-services; + secretsDir = config.nix-bitcoin.secretsDir; +in { + + options.services.lightning-loop = { + enable = mkEnableOption "lightning-loop"; + package = mkOption { + type = types.package; + default = pkgs.nix-bitcoin.lightning-loop; + defaultText = "pkgs.nix-bitcoin.lightning-loop"; + description = "The package providing lightning-loop binaries."; + }; + proxy = mkOption { + type = types.nullOr types.str; + default = null; + description = "Connect through SOCKS5 proxy"; + }; + extraArgs = mkOption { + type = types.separatedString " "; + default = ""; + description = "Extra command line arguments passed to loopd."; + }; + cli = mkOption { + default = pkgs.writeScriptBin "loop" + # Switch user because lnd makes datadir contents readable by user only + '' + exec sudo -u lnd ${cfg.package}/bin/loop "$@" + ''; + description = "Binary to connect with the lnd instance."; + }; + enforceTor = nix-bitcoin-services.enforceTor; + }; + + config = mkIf cfg.enable { + assertions = [ + { assertion = config.services.lnd.enable; + message = "lightning-loop requires lnd."; + } + ]; + + environment.systemPackages = [ cfg.package (hiPrio cfg.cli) ]; + + systemd.services.lightning-loop = { + description = "Run loopd"; + wantedBy = [ "multi-user.target" ]; + requires = [ "lnd.service" ]; + after = [ "lnd.service" ]; + serviceConfig = nix-bitcoin-services.defaultHardening // { + ExecStart = '' + ${cfg.package}/bin/loopd \ + --lnd.host=${config.services.lnd.listen}:10009 \ + --lnd.macaroondir=${config.services.lnd.dataDir}/chain/bitcoin/mainnet \ + --lnd.tlspath=${secretsDir}/lnd-cert \ + ${optionalString (cfg.proxy != null) "--server.proxy=${cfg.proxy}"} \ + ${cfg.extraArgs} + ''; + User = "lnd"; + Restart = "on-failure"; + RestartSec = "10s"; + ReadWritePaths = "${config.services.lnd.dataDir}"; + } // (if cfg.enforceTor + then nix-bitcoin-services.allowTor + else nix-bitcoin-services.allowAnyIP); + }; + }; +} diff --git a/modules/lnd.nix b/modules/lnd.nix index 6a53dec..1a3489f 100644 --- a/modules/lnd.nix +++ b/modules/lnd.nix @@ -159,7 +159,7 @@ in { in [ # Run fully privileged for secrets dir write access "+${nix-bitcoin-services.script '' - attempts=50 + attempts=250 while ! { exec 3>/dev/tcp/127.0.0.1/${restPort} && exec 3>&-; } &>/dev/null; do ((attempts-- == 0)) && { echo "lnd REST service unreachable"; exit 1; } sleep 0.1 diff --git a/modules/modules.nix b/modules/modules.nix index dd6d936..c3965d4 100644 --- a/modules/modules.nix +++ b/modules/modules.nix @@ -13,6 +13,7 @@ ./recurring-donations.nix ./hardware-wallets.nix ./lnd.nix + ./lightning-loop.nix ./secrets/secrets.nix ./netns-isolation.nix ./dbus.nix diff --git a/modules/netns-isolation.nix b/modules/netns-isolation.nix index f9ba6d6..a010425 100644 --- a/modules/netns-isolation.nix +++ b/modules/netns-isolation.nix @@ -127,6 +127,10 @@ in { id = 21; connections = []; }; + lightning-loop = { + id = 22; + connections = [ "lnd" ]; + }; }; systemd.services = { @@ -291,6 +295,14 @@ in { # nginx: Custom netns configs services.nix-bitcoin-webindex.host = mkIf config.services.nix-bitcoin-webindex.enable netns.nginx.address; + # loop: Custom netns configs + services.lightning-loop = mkIf config.services.lightning-loop.enable { + cli = pkgs.writeScriptBin "loop" + # Switch user because lnd makes datadir contents readable by user only + '' + netns-exec nb-lightning-loop sudo -u lnd ${config.services.lightning-loop.package}/bin/loop "$@" + ''; + }; }) # Custom netns config option values if netns-isolation not enabled (mkIf (!cfg.enable) { diff --git a/modules/presets/secure-node.nix b/modules/presets/secure-node.nix index 37afd7d..1f172ba 100644 --- a/modules/presets/secure-node.nix +++ b/modules/presets/secure-node.nix @@ -161,6 +161,12 @@ in { }; services.tor.hiddenServices.lnd = mkIf cfg.lnd.enable (mkHiddenService { port = cfg.lnd.onionport; toHost = cfg.lnd.listen; }); + # lightning-loop + services.lightning-loop = { + proxy = cfg.tor.client.socksListenAddress; + enforceTor = true; + }; + # liquidd services.liquidd = { rpcuser = "liquidrpc"; diff --git a/pkgs/default.nix b/pkgs/default.nix index f710f04..7986379 100644 --- a/pkgs/default.nix +++ b/pkgs/default.nix @@ -11,6 +11,7 @@ generate-secrets = pkgs.callPackage ./generate-secrets { }; nixops19_09 = pkgs.callPackage ./nixops { }; netns-exec = pkgs.callPackage ./netns-exec { }; + lightning-loop = pkgs.callPackage ./lightning-loop { }; pinned = import ./pinned.nix; } diff --git a/pkgs/lightning-loop/default.nix b/pkgs/lightning-loop/default.nix new file mode 100644 index 0000000..f1b7e1a --- /dev/null +++ b/pkgs/lightning-loop/default.nix @@ -0,0 +1,23 @@ +{ pkgs, buildGoModule, fetchurl, lib }: + +buildGoModule rec { + pname = "lightning-loop"; + version = "0.7.0-beta"; + + src = fetchurl { + url = "https://github.com/lightninglabs/loop/archive/v${version}.tar.gz"; + # Use ./get-sha256.sh to fetch latest (verified) sha256 + sha256 = "fbb5ae6dd55002a632a924e41a0bb2ce886eb9e834668be35b312b14e8b68233"; + }; + + subPackages = [ "cmd/loop" "cmd/loopd" ]; + + vendorSha256 = "1g0l09zcic5nnrsdyap40dj3zl59gbb2k8iirhph3257ysa52mhr"; + + meta = with lib; { + description = " Lightning Loop: A Non-Custodial Off/On Chain Bridge"; + homepage = "https://github.com/lightninglabs/loop"; + license = lib.licenses.mit; + maintainers = with maintainers; [ nixbitcoin ]; + }; +} diff --git a/pkgs/lightning-loop/get-sha256.sh b/pkgs/lightning-loop/get-sha256.sh new file mode 100755 index 0000000..cfadcab --- /dev/null +++ b/pkgs/lightning-loop/get-sha256.sh @@ -0,0 +1,27 @@ +#! /usr/bin/env nix-shell +#! nix-shell -i bash -p git gnupg +set -euo pipefail + +TMPDIR="$(mktemp -d -p /tmp)" +trap "rm -rf $TMPDIR" EXIT +cd $TMPDIR + +echo "Fetching latest release" +git clone https://github.com/lightninglabs/loop 2> /dev/null +cd loop +latest=$(git describe --tags `git rev-list --tags --max-count=1`) +echo "Latest release is ${latest}" + +# GPG verification +export GNUPGHOME=$TMPDIR +echo "Fetching Joost Jager's Key" +gpg --keyserver hkps://keyserver.ubuntu.com --recv-keys D146D0F68939436268FA9A130E26BB61B76C4D3A 2> /dev/null +echo "Fetching Alex Bosworth's Key" +gpg --keyserver hkps://keyserver.ubuntu.com --recv-keys DE23E73BFA8A0AD5587D2FCDE80D2F3F311FD87E 2> /dev/null + +echo "Verifying latest release" +git verify-tag ${latest} + +echo "tag: ${latest}" +# The prefix option is necessary because GitHub prefixes the archive contents in this format +echo "sha256: $(git archive --format tar.gz --prefix=loop-${latest//v}/ ${latest} | sha256sum | cut -d\ -f1)" diff --git a/pkgs/netns-exec/src/main.c b/pkgs/netns-exec/src/main.c index aec799c..3271fc0 100644 --- a/pkgs/netns-exec/src/main.c +++ b/pkgs/netns-exec/src/main.c @@ -14,6 +14,7 @@ static char *available_netns[] = { "nb-lnd", + "nb-lightning-loop", "nb-bitcoind", "nb-liquidd" }; diff --git a/pkgs/pinned.nix b/pkgs/pinned.nix index 6fe06e2..212c2fd 100644 --- a/pkgs/pinned.nix +++ b/pkgs/pinned.nix @@ -11,7 +11,9 @@ in bitcoind clightning lnd; - inherit (nixBitcoinPkgsUnstable) electrs; + inherit (nixBitcoinPkgsUnstable) + electrs + lightning-loop; stable = nixBitcoinPkgsStable; unstable = nixBitcoinPkgsUnstable; diff --git a/test/scenarios/default.py b/test/scenarios/default.py index 8d942d3..76c9fcb 100644 --- a/test/scenarios/default.py +++ b/test/scenarios/default.py @@ -88,3 +88,13 @@ succeed("systemctl stop nanopos lightning-charge spark-wallet clightning") succeed("systemctl start lnd") assert_matches("su operator -c 'lncli getinfo' | jq", '"version"') assert_no_failure("lnd") + +### Test loopd + +succeed("systemctl start lightning-loop") +assert_matches("su operator -c 'loop --version'", "version") +# Check that lightning-loop fails with the right error, making sure +# lightning-loop can connect to lnd +machine.wait_until_succeeds( + log_has_string("lightning-loop", "chain notifier RPC isstill in the process of starting") +) diff --git a/test/scenarios/withnetns.py b/test/scenarios/withnetns.py index c79162d..06567c3 100644 --- a/test/scenarios/withnetns.py +++ b/test/scenarios/withnetns.py @@ -150,3 +150,13 @@ succeed("systemctl stop nanopos lightning-charge spark-wallet clightning") succeed("systemctl start lnd") assert_matches("su operator -c 'lncli getinfo' | jq", '"version"') assert_no_failure("lnd") + +### Test loopd + +succeed("systemctl start lightning-loop") +assert_matches("su operator -c 'loop --version'", "version") +# Check that lightning-loop fails with the right error, making sure +# lightning-loop can connect to lnd +machine.wait_until_succeeds( + log_has_string("lightning-loop", "chain notifier RPC isstill in the process of starting") +) diff --git a/test/test.nix b/test/test.nix index 6f64ae8..640820c 100644 --- a/test/test.nix +++ b/test/test.nix @@ -34,6 +34,8 @@ import ./make-test.nix rec { services.lnd.enable = true; systemd.services.lnd.wantedBy = mkForce []; + services.lightning-loop.enable = true; + systemd.services.lightning-loop.wantedBy = mkForce []; services.electrs.enable = true;