Did you ever consider to install a Bitcoin Lightning Network on your own hardware? Here is how I did it.
The mandatory introduction: What is Lightning Network?
Quoting from https://lightning.network/:
Scalable, Instant Bitcoin/Blockchain Transactions
Lightning Network is trying to solve a few of the current problems that the Bitcoin is suffering: scalability (number of transactions per second, right now Bitcoin handles about seven transactions per second), speed (time required to confirm a transaction,), cost (fee to pay for each transaction).
Curiously enough, it turns out that transaction speed and cost are some of the same problems that Bitcoin is solving, but “how long a minute is depends on which side of the bathroom door you’re on”.
How it works?
The basic idea is to create a “channel” between 2 participants (let’s s Alice and Bob) and place some funds in, for example 1 BTC each. This requires a transaction on the Bitcoin blockchain, that locks the funds in a 2 multisig address. Here is the situation of the open channel:
Alice <= 1 BTC == 1 BTC => Bob # opening of a new channel
Alice and Bob can now make as many transactions as they want “off-chain”, they just need to exchange an updated “balance-sheet”: that is transaction signed by the other party (so it’s missing one of the signatures) that is not propagated to the bitcoin mainnet (yet), and that pays the updated funds to the participants.
Because the transaction are stored “off-chain”, Alice and Bob don’t need to wait for a confirmation from the main blockchain (instant transaction) nor to pay a fee to the mainnet miners (low fees).
Here is the situation of the open channel while the transactions are happening:
Alice <= 0.8 BTC == 1.2 BTC => Bob # Bob sent 0.2 BTC to Alice Alice <= 0.9 BTC == 1.1 BTC => Bob # Alice sent 0.1 BTC to Bob Alice <= 0.4 BTC == 1.6 BTC => Bob # Alice sent 0.5 BTC to Bob … …
At any moment, a participant can make the balance-sheet “real”, signing the latest multisig transaction received from (and signed by) the other party, and broadcasting it a as regular Bitcoin transaction: the channel is now closed.
But the real power of the Lightning Network is the “six degrees of separation” idea: a participant can reach any other in just a few hops, if the “routing” is know. So if Alice has an open channel with Bob, and Bob has an open channel with Charlie, Alice can make a transaction with Charlie, and send for example 0.5 BTC to him. Here is the situation of the channels before transaction:
Alice <= 1 BTC == 1 BTC => Bob # Bob has 3 BTC on 2 channels Bob <= 2 BTC == 2 BTC => Charlie
and after the transaction:
Alice <= 0.5 BTC == 1.5 BTC => Bob # Bob is still having 3 BTC Bob <= 1.5 BTC == 2.5 BTC => Charlie
The total balance of Bob remains unchanged, while the balance of Charlie increase of 0.5 BTC and the balance of Alice decreased of 0.5 BTC.
Choosing the Lightning Network implementation to use
At the moment, I found only 2 main solutions that fully conforms to the Lightning Network specification (BOLTs) on the bitcoin mainnet:
- LND, The Lightning Network Daemon(https://github.com/lightningnetwork/lnd)
- c-lightning by Elements Projects(https://github.com/ElementsProject/lightning)
I spent some time reading the features implemented by the each project, and then decide to install LND because:
- Both projects require a full bitcoind backend daemon running on the same host (or an host reachable by net), but LND supports also a bitcoind node in prune mode: this means that you don’t need to store the full bitcoin blockchain locally (more than 200 GB at the moment), so the disk space requirements are less and I my spare 128 GB USB pen drive is more than enough.
- LND has an experimental support for the neutrino backend (only testnet at the moment), a Bitcoin light client, that further reduce the disk space requirements: I bet that soon or later it will reach a production ready quality, and that I could switch to it.
- LND has an automatic channel management (
autopilot
), that creates and manage channels. - LND has “nat” feature: this allow to advertise the node to the network as long as it’s behind a single NAT, automatically handling the change of the public IP address, so that less scripting tricks are required on the node.
Let’s the fun begin: Preparing the Lightning Network node
Before the installation, I read and digested a few good guides, that I have to give credits:
- The official Raspberry Pi guides: https://www.raspberrypi.org/documentation/
- The LND https://github.com/lightningnetwork/lnd/blob/master/docs/INSTALL.md
- The superb Stadicus’s Beginner’s Guide to Lightning on a Raspberry Pi: https://github.com/Stadicus/guides/tree/master/raspibolt
Hardware setup
I had a Raspberry Pi 3 Model B+ mostly unused at home, a spare 128GB USB pen drive and a good network connectivity, so I tried to reuse those components. Please mind that you need to run your node 24/7, so a noiseless host is preferable. I installed some heat sinks on the Pi to avoid to have a fan running all the time while keeping the CPU temperature in a safe range. During the full installation process and further daily running operations, the temperature of the CPU never exceeded 65°C, safely below the max temperature of 85°C: anything above this and the Pi will throttle the CPU frequency to bring the temperature back down.
Setup of operating system on the Raspberry Pi
The installation of the operating system on the Pi is pretty simple: I decided to run an headless host, so I downloaded the Raspbian lite image from the official repository here: https://downloads.raspberrypi.org/raspbian_lite_latest and wrote the image to a MicroSD card, as explained here: https://www.raspberrypi.org/documentation/installation/installing-images/
Remember to add an empty file ssh
in the root of the MicroSD: this is required to activate the ssh remote connectivity, otherwise you can’t login into the Pi from the network and configure it.
Then using the nmap map tool I found the ip address given by my home router to the Pi
> $ nmap -sP 192.168.1.0/24
and started an ssh session using the default user pi
and password raspberry
.
> $ ssh pi@192.168.1.134
To complete the configuration of the Pi, I run the raspi-config
tool from the command line, tweaking the parameters to configure the WiFi SSID and pre-shared key, the locale, the timezone, enabling the ssh connectivity. Because the node is running headless, I also decreased the amount of memory made available to the GPU, in Memory split.
I then read the section “Mounting an HDD” in the raspberry guide https://www.raspberrypi.org/documentation/configuration/external-storage.md to mount my USD pen drive as a
Hardening
Because this is node is connected to the internet and is managing “money”, is better to enter the “paranoia” mode.
In order, what I did:
- Removed the default user pi and created a new user to use as the main user to connect to the Pi.
- Configured the passwordless ssh, following this guide https://www.raspberrypi.org/documentation/remote-access/ssh/passwordless.md and then allowed only the login via SSH certificate, changing the settings
ChallengeResponseAuthentication
andPasswordAuthentication
tono
in the file/etc/ssh/sshd_config
- Created a “service” user
bitcoin
to run the demons required to interact with the Lightning Network, runningsudo adduser bitcoin
What I should also do:
- Install fail2ban to automatically ban IP addresses that make too many password failures. My Pi is behind an home router that doesn’t forward ssh port, so this is not strictly required, but this can prevent attacks generated from other compromised device in my home network (smartphones, laptops, smart tv….)
- Install a firewall
Utilities
Here some handy aliases that I added to ~/.bashrc
to make life easier:
# handy aliases alias ..=’cd ..’ alias …=’cd ../..’ alias ….=’cd ../../..’ alias …..=’cd ../../../..’ alias ……=’cd ../../../../..’ alias 1=’cd -’ alias _=sudo alias afind=’ack -il’ alias d=’dirs -v | head -10' alias l=’ls -lah’ alias la=’ls -lAh’ alias ll=’ls -lh’ alias md=’mkdir -p’ alias rd=rmdir
Remember to reaload the ~/.bashrc
source ~/.bashrc
Then I installed iftop, a useful utility to monitor the network traffic that run in a terminal
sudo apt install iftop
sudo iftop -i wlan0
PuTTY configuration
I use PuTTY to connect to the RaspberryPi. Here is how I changed the default configuration for the connection:
- terminal → keyboard → The function keys and keypad → Linux
- connection → seconds between keepalive → 30
Installing bitcoin core on Raspberry Pi
LND works with bitcoind as backend, so I first installed the latest bitcoin core software (https://bitcoincore.org). I quickly prepared a script, that I can reuse in my next projects, to download bitcoin core, verify the checksum and install the binaries:
BITCOIND_VERSION=0.16.3 ARCH=arm-linux-gnueabihf BITCOIND_ARCHIVE=bitcoin-${BITCOIND_VERSION}-${ARCH}.tar.gz
cd /tmp \ && wget https://bitcoincore.org/bin/bitcoin-core-${BITCOIND_VERSION}/${BITCOIND_ARCHIVE} \ && wget https://bitcoincore.org/bin/bitcoin-core-${BITCOIND_VERSION}/SHA256SUMS.asc \ && wget https://bitcoin.org/laanwj-releases.asc \ && SHA256=`grep "${BITCOIND_ARCHIVE}" SHA256SUMS.asc | awk '{print $1}'` \ && echo $SHA256 \ && echo "$SHA256 ${BITCOIND_ARCHIVE}" | sha256sum -c - \ && gpg --import ./laanwj-releases.asc \ && gpg --verify SHA256SUMS.asc \ && tar -xzf ${BITCOIND_ARCHIVE} \ && sudo install -m 0755 -o root -g root -t /usr/local/bin bitcoin-${BITCOIND_VERSION}/bin/* \ && rm -rf /tmp/* \ && bitcoind --version
As suggested by the Stadicus’s guide, I linked the bitcoind data directory /home/bitcoin/.bitcoin
to a directory on the external USB pendrive:
sudo su bitcoin
ln -s /mnt/hdd/bitcoin /home/bitcoin/.bitcoin
and created the /home/bitcoin/.bitcoin/bitcoin.conf
, activating the prune mode (note the prune=550
parameter).
vim /home/bitcoin/.bitcoin/bitcoin.conf
# Bitcoind options server=1 daemon=1 txindex=0 disablewallet=1 prune=550
# Connection settings rpcuser=XXXXX rpcpassword=YYYYY
onlynet=ipv4 zmqpubrawblock=tcp://127.0.0.1:29000 zmqpubrawtx=tcp://127.0.0.1:29001
# Raspberry Pi optimizations dbcache=100 maxorphantx=10 maxmempool=50 maxconnections=40 maxuploadtarget=5000
Now the time consuming task, the Initial Block Download , the system need to download and verify all the blocks (from 2009) to catch up to the tip of the current best block chain.
I said before that lnd
supports a bitcoin core backend in prune mode, this means that the old blocks are purged from the local disk, but they as still downloaded and validated: I started the process, but I quickly realized that full process would have taken more than 10 days… Someone suggests to use a separated device, with a more powerful CPU, to download and validate the blocks, and then copy the validated blocks on the Pi.
Spawning an Amazon EC2 instance to speedup the IBD
I don’t have a spare device fast enough to leave 24/7 crunching data, so I quickly spawn an m4.large instance on Amazon EC2 with an additional 50 GB EBS volume, visible as /dev/sdf
. I used the amzn-ami-2018.03.a-amazon-ecs-optimized (ami-c91624b0)
, that natively supports docker
, and then run bitcoind using a docker image. Here a quick recap:
- Formatting and mount the additional EBS volume
sudo mkfs.ext4 /dev/sdf sudo mkdir /mnt/bitcoin-data sudo mount /dev/sdf /mnt/bitcoin-data/
- Cloning repository and build the docker image
sudo yum install git
git clone https://github.com/dougvk/lightning-node.git
cd lightning-node
docker build . -t bitcoind
- Running the
bitcoind
node for the first time checking the progress
docker run --name bitcoind_mainnet -d -v /mnt/bitcoin-data/bitcoind:/data -p 8333:8333 bitcoind
docker logs --tail 10 --follow bitcoind_mainnet
- Stopping the bitcoind_mainnet process after a couple of minutes, and adding
prune=550
to config to activate the blocks pruning
docker stop bitcoind_mainnet docker rm bitcoind_mainnet sudo yum install vim sudo vim /mnt/bitcoin-data/bitcoind/bitcoin.conf
- Starting again the bitcoind_mainnet process and wait for the Initial Block Download
docker run --name bitcoind_mainnet -d -v /mnt/bitcoin-data/bitcoind:/data -p 8333:8333 bitcoind
docker logs --tail 10 --follow bitcoind_mainnet
Then I waited for about 15 hours to eventually have progress=1.000000
2018–09–19 18:57:35 UpdateTip: new best=00000000000000000020aaf1f04a1ae2025408d044d936100a65c4b6e22a0853 height=542137 version=0x20000000 log2_work=89.696461 tx=342737382 date=’2018–09–19 18:57:22' progress=1.000000 cache=83.4MiB(410453txo) warning=’6 of last 100 blocks have unexpected version’
The cost of the m4.large instance is $0.10/hour, so it’s an affordable investment.
Now is possible to stop the bitcoind_mainnet process, and transfer the content of
/mnt/bitcoin-data/bitcoind/blocks
/mnt/bitcoin-data/bitcoind/chainstate
/mnt/bitcoin-data/bitcoind/database
to the RaspberryPi in /home/bitcoin/.bitcoin
and terminate the EC2 instance (remember to delete the additional EBS volume too).
Starting bitcoind
Finally I created a systemd service, using this config (“stolen” from the Spadicus’s guide)
sudo vim /etc/systemd/system/bitcoind.service
[Unit] Description=Bitcoin daemon Wants=getpublicip.service After=getpublicip.service [Service] ExecStartPre=/bin/sh -c 'sleep 30' ExecStart=/usr/local/bin/bitcoind -daemon -conf=/home/bitcoin/.bitcoin/bitcoin.conf -pid=/home/bitcoin/.bitcoin/bitcoind.pid PIDFile=/home/bitcoin/.bitcoin/bitcoind.pid User=bitcoin Group=bitcoin Type=forking KillMode=process Restart=always TimeoutSec=120 RestartSec=30 [Install] WantedBy=multi-user.target
and finally started the service
sudo systemctl enable bitcoind.service
sudo systemctl start bitcoind.service
sudo systemctl status bitcoind.service
● bitcoind.service - Bitcoin daemon Loaded: loaded (/etc/systemd/system/bitcoind.service; enabled; vendor preset: enabled) Active: active (running) since Sun 2018-09-19 10:25:05 CEST; 6min ago Process: 9482 ExecStart=/usr/local/bin/bitcoind -daemon -conf=/home/bitcoin/.bitcoin/bitcoin.conf -pid=/home/bitcoin/.bitcoin/bitcoind.pid (code=exited, sta Process: 9353 ExecStartPre=/bin/sh -c sleep 30 (code=exited, status=0/SUCCESS) Main PID: 9485 (bitcoind) CGroup: /system.slice/bitcoind.service └─9485 /usr/local/bin/bitcoind -daemon -conf=/home/bitcoin/.bitcoin/bitcoin.conf -pid=/home/bitcoin/.bitcoin/bitcoind.pid
Installing LND on Raspberry Pi
Finally, I installed LND (https://github.com/lightningnetwork/lnd/blob/master/README.md)
I first prepared a small script, (that I want to reuse for another project: running a Lightning Network node on Docker), to run as the “admin” user:
LND_VERSION=v0.5-beta ARCH=linux-arm64 LND_ARCHIVE=lnd-${ARCH}-${LND_VERSION}.tar.gz
cd /tmp \ && wget -q https://github.com/lightningnetwork/lnd/releases/download/${LND_VERSION}/${LND_ARCHIVE} \ && wget -q https://github.com/lightningnetwork/lnd/releases/download/${LND_VERSION}/manifest-${LND_VERSION}.txt \ && wget -q https://github.com/lightningnetwork/lnd/releases/download/${LND_VERSION}/manifest-${LND_VERSION}.txt.sig \ && wget -q https://keybase.io/roasbeef/pgp_keys.asc \ && SHA256=`grep "${LND_ARCHIVE}" manifest-${LND_VERSION}.txt | awk '{print $1}'` \ && echo $SHA256 \ && sha256sum ${LND_ARCHIVE} \ && echo "$SHA256 ${LND_ARCHIVE}" | sha256sum -c - \ && gpg --import ./pgp_keys.asc \ && gpp --verify manifest-${LND_VERSION}.txt.sig \ && tar -xzf ${LND_ARCHIVE} \ && sudo install -m 0755 -o root -g root -t /usr/local/bin lnd-linux-386-${LND_VERSION}/* \ && rm -rf /tmp/* \ && lnd --version
Now the systemd service startup script, inspired from the Stadicus’s guide:
sudo vim /etc/systemd/system/lnd.service
[Unit] Description=LND Lightning Daemon Wants=bitcoind.service After=bitcoind.service
[Service] ExecStart=/usr/local/bin/lnd PIDFile=/home/bitcoin/.lnd/lnd.pid User=bitcoin Group=bitcoin LimitNOFILE=128000 Type=simple KillMode=process TimeoutSec=180 Restart=always RestartSec=60
[Install] WantedBy=multi-user.target
Because I’m running lnd
with nat
option, there is no need to explicitly set the --externalip=x.x.x.x
startup parameter.
After the lnd daemon was running, I created a new wallet address to use:
lncli create
and then generated a new Bitcoin address to receive funds on-chain:
lncli newaddress np2wkh
and sent some BTC to it.
After a while, lnd started to create new channels on the Lightning Network:
lncli listchannels
{ "channels": [ { "active": true, "remote_pubkey": "02ad6fb8d693dc1e4569bcedefadf5f72a931ae027dc0f0c544b34c1c6f3b9a02b", "channel_point": "a33efe63ce594592cd6f8a3a6e8a68998f0dfa6b02b4e599df8b2f80f5d17f49:1", "chan_id": "596552128324173825", ..... ..... }, { "active": true, "remote_pubkey": "02fcc4b0a87749b022f05568d5ef893b7893bcbaa99276bbbb55d9e9abc1bdced0", "channel_point": "878461d5557ba4b72951a1d0d02439ff8ed076a25a585b8b75e3666778e53895:0", "chan_id": "596558725467734016", ..... ..... } ] }
Let’s the (no) Profit begin
I’m running my Lightning Network node for only few days, but it’s clear that, as expected, is not generating any profit at all:
lncli feereport
{ "channel_fees": [ … … … ], "day_fee_sum": "0", "week_fee_sum": "0", "month_fee_sum": "0" }
Why? ok, Lightning Network is still is it’s infancy, but IMHO the root cause is the topology of the network itself: it’s not distributed at all.
Let me explain this better: according to https://1ml.com/ today we have 3,544 nodes, providing a network capacity of 112.16 BTC in 12,160 channels, but guess what: the top 20 nodes by BTC capacity, just the 0.56% of them, control 85% of the total BTC capacity and 33% of total channels.
So it’s easy to figure out that the actual topology is a based on few “super hubs” that route the big part of the payments, and get the fees.
Is this a bad thing®? I’ll expose my opinion in a upcoming story.
Feel free to connect my Lightning Network: >$ lncli connect 039401f72bc0d40efb58b01de15527a2a5ae1943d7c29067b725a1467a93c7e66f@2.238.144.76:9735