MCPcopy Index your code
hub / github.com/NYDIG-OSS/lnmux

github.com/NYDIG-OSS/lnmux @v1.0.0

Chat with this repo
repository ↗ · DeepWiki ↗ · release v1.0.0 ↗ · + Follow
597 symbols 1,208 edges 40 files 239 documented · 40%
What it actually does AI analysis from the code graph — generated when you open this
loading…
README

Lightning Multiplexer

This repository contains the code for the lnmux service. The problem that lnmux solves is fail-over for incoming Lightning payments. BOLT11 invoices are bound to a single node. If that node goes down, the invoice is no longer payable. The same is true for the node or its peers running out of inbound liquidity.

What lnmux allows you to do is set up a cluster of nodes where each of these nodes can settle any invoice. If a node is unable to accept payments for one of the reasons above, senders will still be able to pay the invoice via one of the other nodes.

Even with all nodes in the cluster available, it can be advantageous for the sender to have multiple routing options to better utilize liquidity and minimise fees. If needed, multi-part payments can be completed through more than one cluster node.

Running

For the setup, it is assumed that there are multiple LND nodes running with connections to the wider Lightning network and sufficient inbound liquidity. The minimally required version of lnd is v0.15.0-beta.

  • Run the lnd nodes with the --requireinterceptor option. This ensures that HTLCs are always offered to interceptor applications eventually, even when there are momentary disconnects. Not running with this option can lead to HTLCs failing after the invoice that they pay to has been marked as settled.

  • Create a postgres database. Note that lnmux uses stateless invoices. This means that the database only contains settled invoices.

  • Create a config file for lnmuxd named lnmux.yml. An example can be found in this repository. The config file contains the following elements:

  • LND nodes configuration: TLS certificate, macaroon, address and pubkey. Pubkey is configured as a protection against unintentionally connecting to the wrong node.
  • Postgres connection string for the database created above (can be overridden by the environment variable LNMUX_PERSISTENCE_POSTGRES_DSN to avoid exposing credentials).
  • 32-byte Identity PRIVATE key. This key is used to generate invoices and decode incoming htlcs. In a production environment, this key must be protected carefully (can be overridden by the environment variable LNMUX_IDENTITY_KEY to avoid exposing the private key).
  • Your logging configuration. The most important options are the logs level, the encoding format and the activation of grpc logs.

  • Initialize the database: go run ./cmd/lnmuxd -c lnmux.yml migrate init

  • Migrate the database to the latest version: go run ./cmd/lnmuxd -c lnmux.yml migrate up

  • Run lnmuxd: go run ./cmd/lnmuxd -c lnmux.yml run. This opens connections to all LND nodes via the HTLC interceptor API. Incoming htlcs are collected and assembled into sets. When a set is complete, an external application connected to lnmux decides whether to settle the invoice. If the application sends a settle request, the invoice is marked as settled in the lnmux database and a settle action is returned to the LND instance(s) holding the HTLC(s).

  • Invoice generation is taken over by lnmuxd. It is no longer a responsibility of the LND nodes. To generate an invoice, run:

``bash grpcurl -plaintext -v \ -d '{"amt_msat":20000, "expiry_secs":600}' \ localhost:9190 lnmux.Service.AddInvoice. ``` If you decode the invoice, you'll find route hints from each node in the cluster to thelnmuxdpublic key.lnmuxd` acts as a virtual node without real channels.

The invoice is not stored in the lnmux database and does not take up any disk space. This makes lnmux particularly suitable for scenarios where large numbers of invoices are generated.

Below is an example invoice generated by lnmuxd. json { "destination": "03422175ba6fed348de4f273cf81627c26d5ab2a78bfdc1a39e6f9c06354dd9371", "payment_hash": "fd3b0f9a077006697ba8f82cc5673d1511cb11d1d1012662bf0c0b3c93f4245e", ... "route_hints": [ { "hop_hints": [ { "node_id": "020723c6f2203f1072336bd0e71bf4d11e367ab0b3010ce60c080abef0d4770db8", "chan_id": "12345", "fee_base_msat": 0, "fee_proportional_millionths": 0, "cltv_expiry_delta": 40 } ] }, { "hop_hints": [ { "node_id": "026ff75cb2ff49b864833aa3c93970069070231a9ad64819252e190406dd0a6976", "chan_id": "12345", "fee_base_msat": 0, "fee_proportional_millionths": 0, "cltv_expiry_delta": 40 } ] } ], "payment_addr": "8f135e331fed1858e467f775fa644751a2b0da17ee0676ebb1bc8a994d667be6", "num_msat": "6000", "features": { ... } }

  • It should be possible to pay this invoice as long as at least one node in the cluster is available.

  • When the payment comes in, an event is generated. Events can be listened for via:

```bash grpcurl -plaintext -v localhost:9190 lnmux.Service.SubscribeInvoiceAccepted ````

Each time an invoice is accepted, you will receive a pair (hash , setID):

json Response contents: { "hash": "rCdcdDvpo7oqkO1Gkh1hikofseQpUZhgAIaw6gYXz7M=", "setId": "J1q7YDvixChKrJjjP/yvv16KiQdpHqwW7gy8jqxDAvI=" }

  • To request settlement of the invoice, invoke:

bash grpcurl -plaintext -v \ -d '{"hash":"rCdcdDvpo7oqkO1Gkh1hikofseQpUZhgAIaw6gYXz7M=","set_id":"J1q7YDvixChKrJjjP/yvv16KiQdpHqwW7gy8jqxDAvI="}' \ localhost:9190 lnmux.Service.SettleInvoice`

Expected output

If you've set up lnmuxd correctly, output similar to what is shown below is expected. * Interception is started on all of your nodes * When an HTLC comes in, it is matched against the invoice database. If there is a match, the invoice is loaded into memory. * The total amount of the HTLC set is tracked. When the total amount matches the invoice amount, HTLC settle resolutions are sent to LND.

2022-04-19T08:39:09.333+0200    INFO    Succesfully connected to LND    {"node": "020723c6f2203f1072336bd0e71bf4d11e367ab0b3010ce60c080abef0d4770db8"}
2022-04-19T08:39:09.339+0200    INFO    Succesfully connected to LND    {"node": "026ff75cb2ff49b864833aa3c93970069070231a9ad64819252e190406dd0a6976"}
2022-04-19T08:39:09.347+0200    INFO    InvoiceRegistry starting
2022-04-19T08:39:09.347+0200    INFO    Press ctrl-c to exit
2022-04-19T08:39:09.347+0200    DEBUG   Starting htlc interception  {"node": "026ff75cb2ff49b864833aa3c93970069070231a9ad64819252e190406dd0a6976"}
2022-04-19T08:39:09.347+0200    DEBUG   Starting htlc interception  {"node": "020723c6f2203f1072336bd0e71bf4d11e367ab0b3010ce60c080abef0d4770db8"}
2022-04-19T08:39:09.347+0200    DEBUG   Starting main event loop
2022-04-19T08:39:23.166+0200    INFO    Htlc received   {"hash": "fd3b0f9a077006697ba8f82cc5673d1511cb11d1d1012662bf0c0b3c93f4245e", "source": "026ff75cb2ff49b864833aa3c93970069070231a9ad64819252e190406dd0a6976", "circuitKey": "1161084279062528:7"}
2022-04-19T08:39:23.224+0200    DEBUG   Loaded invoice from db  {"hash": "fd3b0f9a077006697ba8f82cc5673d1511cb11d1d1012662bf0c0b3c93f4245e"}
2022-04-19T08:39:23.225+0200    DEBUG   Hodl subscribe for 1161084279062528:7
2022-04-19T08:39:23.225+0200    DEBUG   Htlc accepted: hash=fd3b0f9a077006697ba8f82cc5673d1511cb11d1d1012662bf0c0b3c93f4245e, amt=6000 mSAT, expiry=1110, circuit=1161084279062528:7, mpp=total=6000 mSAT, addr=8f135e331fed1858e467f775fa644751a2b0da17ee0676ebb1bc8a994d667be6
2022-04-19T08:39:23.237+0200    DEBUG   Sending settle resolution   {"hash": "fd3b0f9a077006697ba8f82cc5673d1511cb11d1d1012662bf0c0b3c93f4245e", "source": "026ff75cb2ff49b864833aa3c93970069070231a9ad64819252e190406dd0a6976", "circuitKey": "1161084279062528:7", "outcome": "settled"}

Leader election

Lnmux is designed as a single-instance process. Running multiple instances connected to the same database simultaneously results in incorrect behavior and may put funds at risk.

To ensure that there can only ever be one instance running, k8s leader election can be enabled via the DistributedLock config group.

Invoice lifecycle

Note that only the states accepted, settle requested and settled are published to callers of the SubscribeSingleInvoice rpc. Lnmux isn't aware of open invoices because it is stateless.

The transition from settle requested to settled can be made more robust in the future. This transition is happening already, but not backed by an actual final settle event from lnd. See https://github.com/lightningnetwork/lnd/issues/6208.

Docker Instructions

We provide a Dockerfile to launch lnmux using docker.

This Dockerfile will build 2 different images: one for debian and the other one for alpine.

You have 2 options to get the lnmux image:

  • Either, building it by yourself: navigate to the lnmux repository and simply do: bash docker build -t lnmux .

  • Or pull directly the image we pushed into ghcr.io (You can check existing images here) bash docker pull ghcr.io/bottlepay/lnmux:{tag|sha}

However, we don't provide any configuration file in the Dockerfile (and don't do the migration either). You need to provide certificates (TLS/Macaroon) as well to connect lnd nodes.

To do so, you can simply use --mount:

 docker run -it \
  --mount type=bind,source="$(pwd)/lnmux.yml",target=/app/lnmux.yml \
  --mount type=bind,source="$(pwd)/lnd/certs",target=/app/certs  \
  ghcr.io/bottlepay/lnmux:sha-bf41263-alpine /bin/sh -c \
  "/usr/bin/lnmuxd -c /app/lnmux.yml migrate init && \
   /usr/bin/lnmuxd -c /app/lnmux.yml migrate up && \
   /usr/bin/lnmuxd -c /app/lnmux.yml run"

⚠️ Don't forget to add before starting nodes connected to lnmux (aka Alice and Bob): - --requireinterceptor to make sure all htlcs are intercepted - --tlsextradomain=host.docker.internal to add docker DSN to TLS certs (otherwise you can't connect from a docker container)

If your nodes are already started, you will have to stop them, remove manually tls certs and then restart them.

Furthermore, you must use host.docker.internal instead of localhost in your configuration (plus --add-host host.docker.internal:host-gateway for Linux users).

Regtest testing

The minimal setup to test on regtest is to create three LND nodes Alice, Bob and Carol. Alice and Bob should have a channel with Carol, and lnmuxd should be connected to Alice and Bob.

The typical workflow is: - Create invoices using lnmux - Carol pays the invoices - lnmux generates accepted events and settles the invoices either through Alice or Bob.

Experimental

This software is in an experimental state. At this stage, breaking changes can happen and may not always include a database migration. Use at your own risk.

Extension points exported contracts — how you extend this code

Payload (Interface)
Payload abstracts access to any additional fields provided in the final hop's TLV onion payload. [2 implementers]
interface.go
LndClient (Interface)
(no doc) [3 implementers]
lnd/client.go
HtlcResolution (Interface)
HtlcResolution describes how an htlc should be resolved.
resolution.go
InvoiceCallback (FuncType)
(no doc)
invoiceregistry.go
AcceptCallback (FuncType)
(no doc)
invoiceregistry.go

Core symbols most depended-on inside this repo

Error
called by 19
lnmuxrpc/lnmux.pb.validate.go
NewFailResolution
called by 12
resolution.go
RequestSettle
called by 9
invoiceregistry.go
newSetID
called by 9
set_id.go
PubKey
called by 8
lnd/client.go
Timeout
called by 8
test/timeout.go
Get
called by 8
persistence/pg.go
Run
called by 7
mux.go

Shape

Method 387
Struct 95
Function 94
Interface 11
FuncType 5
TypeAlias 5

Languages

Go100%

Modules by API surface

lnmuxrpc/lnmux.pb.go131 symbols
lnmuxrpc/lnmux.pb.validate.go128 symbols
lnmuxrpc/lnmux_grpc.pb.go53 symbols
invoiceregistry_test.go36 symbols
invoiceregistry.go32 symbols
htlc_set.go25 symbols
persistence/pg.go16 symbols
lnd/mock_lnd_client.go12 symbols
lnd/client.go12 symbols
cmd/lnmuxd/server.go12 symbols
htlc_sets.go11 symbols
cmd/lnmuxd/config.go10 symbols

Datastores touched

postgresDatabase · 1 repos

For agents

$ claude mcp add lnmux \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact