GithubHelp home page GithubHelp logo

lnd_client's Introduction

LndClient ⚡

Connects to the Lightning Network Daemon's gRPC API known as LND

Donations

This library is being built in the wild according to these principles

  • Free to use
  • Developer friendly
  • Modular

Arguably, the most important part is that it is unbiased.

If you want to keep it that way, and want to promote its active development, please send donations here: bc1qwpj2nyunrvjkj7z0unk4gg3ht26h2ysh9dqtez

Thank you!

Prerequisites for umbrel users

with a fresh clone of this project, run

mix deps.get

copy those files from the umbrel to the computer running the app

  • /home/umbrel/umbrel/lnd/tls.cert must be copied to ~/.lnd/umbrel.cert
  • add /home/umbrel/umbrel/lnd/data/chain/bitcoin/mainnet/readonly.macaroon to the ~/.lnd
  • look below for the NODE environment variable that must be set when you run iex -S mix

How to use as a dependency

The package can be installed by adding lnd_client to your list of dependencies in mix.exs:

def deps do
  [
    {:lnd_client, "~> 0.1.7"}
  ]
end

Usage

This may be used with another Elixir application or on its own using IEx.

How to use in an app

Add this dependency in mix.exs

{:lnd_client, "~> 0.1"}

LND config

Whenever you start a LndClient GenServer, you need to specify the credential config.

conn_config = %LndClient.ConnConfig{
  node_uri: System.get_env("BOB_NODE"),
  cert_path: System.get_env("BOB_CERT"),
  macaroon_path: System.get_env("BOB_MACAROON")
}

Start the server, get node info and then stop the server

LndClient.start_link(%LndClient.Config{conn_config: conn_config})
LndClient.get_info
LndClient.stop

Multiple LNDs

You can start multiple GenServers by passing in the name:

LndClient.start_link(%LndClient.Config{conn_config: conn_config, name: BobLndClient})
LndClient.get_info(BobLndClient)

How to use it with a Supervisor

Add this to the list of children:

children = [
  # ...
] ++ LndClient.child_specs(%LndClient.Config{conn_config: conn_config})

If you're going to make more than one connection to an LND, pass in the name.

children = [
  # ...
] ++ LndClient.child_specs(%LndClient.Config{conn_config: alice_conn_config, name: AliceLndClient}) ++ LndClient.child_specs(%LndClient.Config{conn_config: bob_conn_config, name: BobLndClient})

Then, somewhere else in your app:

LndClient.add_invoice(%Lnrpc.Invoice{value_msat: 150_000}, BobLndClient)

How to use with IEx

In the root of the folder, ensure that the following env vars in the example below exist:

NODE=localhost:100009
CERT=~/path/to/tls.cert
MACAROON=~/path/to/macaroon

Then iex -S mix

See if the connection was made:

LndClient.get_info

You didn't need to call start_link because .iex.exs calls that for you.

Tests

Run mix test

Library Maintenance

Get fresh protos

List of protos

Make sure protoc is properly installed. Here is how to do it on Debian.

sudo apt install -y protobuf-compiler
mix escript.install hex protobuf
asdf reshim
cd proto

curl -O https://raw.githubusercontent.com/lightningnetwork/lnd/master/lnrpc/lightning.proto
curl -O https://raw.githubusercontent.com/lightningnetwork/lnd/master/lnrpc/routerrpc/router.proto
curl -O https://raw.githubusercontent.com/lightningnetwork/lnd/master/lnrpc/invoicesrpc/invoices.proto

protoc --elixir_out=plugins=grpc:../lib/gRPC lightning.proto
protoc --elixir_out=plugins=grpc:../lib/gRPC router.proto
protoc --elixir_out=plugins=grpc:../lib/gRPC invoices.proto

cd ..

HTLC examples

Routerrpc.ForwardEvent

%Routerrpc.HtlcEvent{
  event: {:forward_event,
   %Routerrpc.ForwardEvent{
     info: %Routerrpc.HtlcInfo{
       incoming_amt_msat: 11005,
       incoming_timelock: 680165,
       outgoing_amt_msat: 11000,
       outgoing_timelock: 680125
     }
   }},
  event_type: :FORWARD,
  incoming_channel_id: 744146171265875972,
  incoming_htlc_id: 87,
  outgoing_channel_id: 742921315233366017,
  outgoing_htlc_id: 379,
  timestamp_ns: 1619026298906259040
}

Routerrpc.ForwardFailEvent

%Routerrpc.HtlcEvent{
  event: {:forward_fail_event, %Routerrpc.ForwardFailEvent{}},
  event_type: :FORWARD,
  incoming_channel_id: 744146171265875972,
  incoming_htlc_id: 88,
  outgoing_channel_id: 742921315233366017,
  outgoing_htlc_id: 380,
  timestamp_ns: 1619028533664696456
}

Routerrpc.SettleEvent

%Routerrpc.HtlcEvent{
  event: {:settle_event, %Routerrpc.SettleEvent{}},
  event_type: :RECEIVE,
  incoming_channel_id: 744146171265875972,
  incoming_htlc_id: 90,
  outgoing_channel_id: 0,
  outgoing_htlc_id: 0,
  timestamp_ns: 1619028715648844495
}

Routerrpc.LinkFailEvent

%Routerrpc.HtlcEvent{
  event: {:link_fail_event,
   %Routerrpc.LinkFailEvent{
     failure_detail: :INVALID_KEYSEND,
     failure_string: "invalid keysend parameters",
     info: %Routerrpc.HtlcInfo{
       incoming_amt_msat: 10000,
       incoming_timelock: 680090,
       outgoing_amt_msat: 0,
       outgoing_timelock: 0
     },
     wire_failure: :INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS
   }},
  event_type: :RECEIVE,
  incoming_channel_id: 744146171265875972,
  incoming_htlc_id: 89,
  outgoing_channel_id: 0,
  outgoing_htlc_id: 0,
  timestamp_ns: 1619028709202674659
}

lnd_client's People

Contributors

codlco avatar ramontayag avatar roosoft avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

lnd_client's Issues

Creating a supervisor to start other servers?

In .iex.exs, three servers are started:

LndClient.Tools.HtlcUpdates.start_link
LndClient.Tools.InvoiceUpdates.start_link
LndClient.Tools.ChannelUpdates.start_link

What do you think about having a supervisor manage these?

defmodule LndClient.Application do

  @moduledoc false

  use Application

  @impl true
  def start(_type, args) do
    children = [
      LndClient.Tools.HtlcUpdates,
      LndClient.Tools.InvoiceUpdates,
      LndClient.Tools.ChannelUpdates,
    ]

    opts = [strategy: :one_for_one, name: LndClient.Supervisor]
    Supervisor.start_link(children, opts)
  end

end

Then in the mix file we make the appropriate changes:

  def application do
    [
      mod: LndClient.Application,
    ]
  end

Since I hope to use this in a Phoenix app, this should just start all the apps under LndClient, such that they can be called to get info like invoice updates (will ask about this in a separate issue btw).

How to use invoice subscriptions?

This is where my lack of Elixir knowledge shows, and it's not necessarily lnd_client specific, but after 30 mins of Googling I have not found the answer.

How do you tap into LndClient.Tools.InvoiceUpdates? I see that when I pay an invoice created using #6, the invoice is printed on the console.

What I imagine is running a function, defined in my app, with the invoice struct as an arg so I can do something about it in my app (i.e. update the database that the invoice has been paid and ping a webhook url).

Ideas on implementation of `SubscribeSingleInvoice`

This assumes #19 has been merged in.

I was hoping that the invoice subscription added in #19 could do what I need (to get hold invoice updates), but while testing, I noticed that none of the hold invoice updates were appearing. Apparently, we need SubscribeSingleInvoice to get updates for hold invoices.

SubscribeSingleInvoice is a bit more challenging to implement because you listen to a single invoice. In this discussion, I'd like to get your feedback on some thoughts I have about implementing it.

Just like #19, I'd like it to be super easy for devs to listen to hold invoice updates. I think it can be achieved with these public interfaces:

1. Defining how it will be consumed

Devs, much like the invoice subscriber change in #19, can define the module and override a function that'll be called whenever there's an update:

defmodule MyApp.SingleInvoiceSubscriber do
  use LndClient.SingleInvoiceSubscriber

  @impl LndClient.SIngleInvoiceSubscriber
  def handle_invoice_update(invoice) do
    # Here, if the invoice is `ACCEPTED`, I can settle the original invoice that is wrapped w/in the hold invoice,
    # so that I can claim the hold invoice with the preimage
  end
end

2. Adding this module to LndClient.child_specs

Just like #19, this module will be passed on to LndClient.child_specs

children ++ LndClient.child_specs(%{
  conn_config: conn_config,
  subscribers: [
    MyApp.SomeOtherSubscriber,
    MyApp.SingleInvoiceSubscriber
  ]
})

LndClient.child_specs knows that LndClient.SingleInvoiceSubscriber was added and returns two processes to start:

a. LndClient.SingleInvoice.Enqueuer. This is a genserver. When it starts, it fetches all invoices in LND, and calls on LndClient.SingleInvoice.DynamicSupervisor to create a process per unsettled hold invoice it sees. I initially didn't think of having this, but when I thought about how to recover from a restart / deploy where the state of a genserver is lost, this was the simplest thing I thought of. It means that all hold invoices are subscribed to, though.
b. LndClient.SingleInvoice.DynamicSupervisor. Because we have to listen to each hold invoice individually (there is no single feed like SubscribeInvoices), then a DynamicSupervisor is required to create genservers on the fly and manage them reliably. The genserver that this DynamicSupervisor creates is the custom one, MyApp.SingleInvoiceSubscriber.

3. There is no number 3, but...

it is worth mentioning that when LndClient.add_hold_invoice is called, LndClient.SingleInvoice.DynamicSupervisor.subscribe will subsequently be called. This function is the same one LndClient.SingleInvoice.Enqueuer will call to listen to the hold invoice that is newly created. There isn't an extra step needed for the dev to listen to the invoice. If MyApp.SingleInvoiceSubscriber isn't in the subscribers: option of LndClient.child_specs, then LndClient.SingleInvoice.DynamicSupervisor.subscribe isn't called or will at least do nothing.

Starting with a supervisor

I was wondering how to allow creation of multiple genservers when an app would talk to multiple. Patterning it after what I've seen elsewhere (to allow a supervisor to watch it), something like this could work:

defmodule Webapp.Application do

  def start(_type, _args) do
    children = [
      # ...
      {
        LndClient,
        %{
          node_uri: System.get_env("NODE"),
          cert_path: System.get_env("CERT"),
          macaroon_path: System.get_env("MACAROON")
        },
      },
    ]

    opts = [strategy: :one_for_one, name: Webapp.Supervisor]
    Supervisor.start_link(children, opts)
  end

end

This means changing LndClient.start_link to something like this (sorry for the code, I just wanted to get it to work):

  def start_link(%{node_uri: node_uri, cert_path: cert_path, macaroon_path: macaroon_path}) do
    state = %{
      node_uri: node_uri,
      cert_path: cert_path,
      macaroon_path: macaroon_path
    } |> init_subscriptions

    result = GenServer.start_link(__MODULE__, state, name: __MODULE__)

    GenServer.call(__MODULE__, :connect, :infinity)

    result
  end

My thinking behind this is once it starts, it makes a connection to LND. This way, if it shuts down and restarted by the supervisor, the connection will get re-established.

There are other things to think about, such as updating convenience methods to easily work with named processes (which is what is recommended when handling mutliple processes), but how does the general direction of this look?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.