LNDのREST APIでLightning Networkでのやりとりを試してみる


ブロックチェーン、Bitcoinといったことに取り組んでいくと、Layer2、Lightning Networkといった言葉にもふれることになっていくと思います。

ローカル環境で、Lightning Networkのやりとりを、ちょこっと試してみたいと思っても、環境の構築などに少し手こずってしまう場合もあるかと思います。そこで、環境を簡単に構築し、Jupyter Notebookでぽちぽちすることでやりとりの動作を確認できるものを用意してみました。

Lightning Network の実装としては、c-lightningeclair など、いくつかありますが、今回は、githubでのstarが多い lnd を使用します。

複数のlndノードをDockerコンテナとして稼働させ、Jupyter Notebookに記載した処理をぽちぽちして、各ノードにリクエストを送っていきます。そのため、lncliではなく、REST APIを使用します。やりとりの流れ・コマンドについては、こちらを参考に、やってみることにします。

本記事は、Lightning Networkについて学びはじめたというような方を想定しての内容です。Jupyter Notebook側に記載した処理内容を参照しながら、読み進めていただければと思います。

シナリオ

Alice, Bob, Charlieの3人分のノードが登場します。資金をやりとりするAliceとCharlieの間にBobがいて、AliceとBobおよびBobとCharieの間ではそれぞれchannelがオープンしているものの、AliceとCharlieの間ではchannelはオープンしていません。AliceがBobのノードを経由して、Charlieに支払うというシンプルなものです。簡単にするため、反対方向のやりとりはしません。

環境

  • Bitcoin Core : 0.19.1
  • lnd : v0.9.2-beta
  • Jupyter Notebook

なお、各ノードはDockerコンテナとして稼働させます。

操作概要

ここでの説明では、lndを操作するコマンドに相当する、リクエストの内容を次のフォーマットで記載しています。REST APIのURLとlncliのコマンドは名称が異なるものも多いですので、それらの対応がわかるよう < > 内にlncliでの名称を記載しています。なお、REST APIの詳細はこちらのサイトにあります。

HTTPメソッド URL {
  パラメータ
}

1. 資金の準備

Lightning Networkで資金(ここではBitcoin)をやりとりするにあたり、まずはwalletに資金を準備します。サンプルでは、Aliceが 0.09 btc (9,000,000 satoshi)、Bobが 0.08 btc 持つようにしています。

1.1 アドレスの生成

// リクエスト <lncli newaddress>
GET /v1/newaddress {
  "type": "np2wkh"
}

// レスポンス
{
  "address": "..."
}

1.2 アドレスへの送金

Bitcoin側で操作します。上記アドレス宛に資金をsendtoaddressし、
generatetoaddressして、そのトランザクション含むブロックを生成します。

1.3 残高確認

// リクエスト <lncli walletbalance>
GET /v1/balance/blockchain

// レスポンス
{
  "total_balance": "...",
  "confirmed_balance": "...",
  "unconfirmed_balance': "..."
}

2. ノード間の接続

AliceとBob, BobとCharlieのノードをそれぞれ接続します。

2.1 identity pubkeyの取得

// リクエスト <lncli getinfo>
GET /v1/getinfo

// レスポンス
{
  ...
  "identity_pubkey": "...",
  ...
}

2.2 相手ノードとの接続

// リクエスト <lncli connect>
POST /v1/peers {
  "addr": {
    "pubkey": "接続先のidentity pubkey",
    "host": "接続先のホスト名:ポート番号"
  }
}

2.3 接続の確認

AliceとCharlieのノードでは1つ、Bobのノードでは2つの情報が表示されます。

// リクエスト <lncli listpeers>
GET /v1/peers

// レスポンス
{
  "peers": [ ※配列
    {
      "pub_key": "接続先のidentity pubkey",
      "address": "接続先のホスト名:ポート番号",
      ...
    },
    ...
  ]
}

3. channelのオープン

AliceとBob, BobとCharlieとの間で、それぞれchannelをオープンします。

サンプルでは、AliceはBobとのchannelに7,000,000 satoshi (0.07 btc)、BobはCharlieとのchannelに6,000,000 satoshi (0.06 btc)を用意しています。前者のchannelのAlice側の状態をみると、capacityが7,000,000 satoshi、残高が6,990,950 satoshi、
コミットメントトランザクションの手数料の額 commit_fee が 9,050 satoshi になっています。

3.1 channelのオープン

// リクエスト
POST /v1/channels {
  "node_pubkey_string": "channelの相手のidentity pubkey",
  "local_funding_amount": "channelに用意する額",
  "push_sat": "初期状態にてchannelの相手に渡す額"
}

// レスポンス
{
  "funding_txid_bytes": "...",
  "output_index": ...
}
Bitcoin側でブロックを生成し、Fundingトランザクションをブロックに含めます。

3.2 channelの確認

// リクエスト <lncli listchannels>
GET /v1/channels

// レスポンス
{
  "channels": [ ※配列
    {
      ...
      "remote_pubkey": "...",
      "channel_point": "...",
      "chan_id": "...",
      "capacity": "...",
      "local_balance": "...",
      "remote_balance": "...",
      "commit_fee": "...",
      ...
      "total_satoshis_sent": "...",
      "total_satoshis_received": "...",
      ...
    },
    ...
  ]
}

4. invoiceの作成

支払先のCharlieがinvoiceを作成します。

4.1 invoiceの作成

// リクエスト <lncli addinvoice>
POST /v1/invoices {
  "value": "請求額",
}

// レスポンス
{
  "r_hash": "...",
  "payment_request": "...",
  "add_index": "..."
}

4.2 invoiceの確認

// リクエスト <lncli lookupinvoice>
GET /v1/invoice/{r_hash_str}

// レスポンス
{
  ...
  "value": "...",
  ...
  "settled": false,
  "payment_request": "...",
  ...
  "state": "OPEN",
  ...
}

5. 支払い

Aliceが支払います。

5.1 支払いの実行

// リクエスト
POST /v1/channels/transactions {
  "payment_request": "4.2 のレスポンスのpayment_request"
}

// レスポンス
{
  ...
  "payment_route": {
    ...
    "hops": [
      {
        "chan_id": "channel識別子",
        "chan_capacity": "...",
        ...
        "amt_to_forward_msat": "...",
        "fee_msat": "...",
        "pub_key": "途中で通るBobノードのidentity pubkey"
        ...
      },
      {
        "chan_id": "channel識別子",
        "chan_capacity": "...",
        ...
        "amt_to_forward_msat": "...",
        "fee_msat": "0",
        "pub_key": "最終宛先のCharlieノードのidentity pubkey"
        ...
      }
    ],
    "total_fees_msat": "手数料の総額",
    "total_amt_msat":  "送金額と手数料の総額"
  },
  ...
}

5.2 残高の確認

支払元Alice・支払先Charlieで残高を確認してみます。

// リクエスト <lncli channelbalance>
GET /v1/balance/channels

// レスポンス
{
    "balance": "...",
    ...
}

6. channelのクローズ

6.1 channelのクローズ

// リクエスト <lncli closechannel>
DELETE /v1/channels/{channel_point.funding_txid_str}/{channel_point.output_index}

// レスポンス
{
  "result": { 
    "chan_close": {
      "closing_txid":"...",
      ...
     }
   }
}
Bitcoin側でブロックを生成し、Closingトランザクションをブロックに含めます。

6.2 残高確認

channelの残高が0になっていることを確認します。

// リクエスト
GET /v1/balance/channels

// レスポンス
{
  "balance": "0",
  "pending_open_balance": "0"
}

Charlieのwallet残高が受領分増えたことを確認します。

// リクエスト
GET /v1/balance/blockchain

// レスポンス
{
  "total_balance': "...",
  "confirmed_balance': "...",
  "unconfirmed_balance': "..."
}

おわりに

Lightning Networkでの資金のやりとりについて、lndのREST APIを使用してJupyter Notebook上でぽちぽち進めてみるときのリクエストを中心に書いてみました。Jupyter Notebook上で、ぽちぽちしたり、変更して動かしてみたりして、Lightning Networkについての理解が何らかの形で進むことに、記事が前提としているサンプルなどがお役に立てばと思います。