c-lightningのplugin機能を触ってみる

#### c-lightningは拡張できる！

ライトニングノード実装として2番目に人気のあるc-lightningには任意のプログラミング言語でプラグインを記述することでライトニングノードの挙動を拡張できるplugin機能があります。

Pluginが作りたくなった方は、このリンクからドキュメントを参照してください
→https://lightning.readthedocs.io/PLUGINS.html

c-lightningチームやユーザーによって様々なプラグインが作成されており、公開されているプラグインにはビットコインノードの代わりにブロックチェーンエクスプローラからトランザクション情報を取ってくるもの、ログを出力してバックアップするものなど様々なものがあります。

また、オープンソース化されて公開されているものの他にも相当数が開発され、ライトニングアプリのバックエンドとして活躍しているものと思われます。

今回はそんなc-lightningのplugin機能を使って簡単なプラグインを作ってみましょう。

### Pluginで使える機能

#### Methods
ここでいうMethodsはPlugin側から呼び出せるライトニングノードの機能のことです。Pluginの動作の中でどこかに送金したり、メッセージに署名したりすることができます。APIでできることと同じです。

#### Event Notifications
Event Notificationsは特定のイベントが発生した際にPlugin内で通知を受け取ることができる機能です。例えばルーティングが発生した際にログに書き出すなどの動作が考えられます。

Notificationsはたくさんありますが、2つ例を挙げると下記のようなものがあります：

- connected: 新しくピアに接続したとき
- forward_event: 送金を中継しようとしたとき

#### Hooks
Hooksは特定のトリガーが発生した際に、ライトニングノードで処理する前にPluginで処理を行い、返り値をライトニングノードに渡してから処理を続行することができる機能です。ライトニングノードの動作の途中でPluginを挟むわけですね。

例えば以下のようなHooksがあります：

- rpc_command: RPCでコマンドを受信した際に、本来の挙動と異なるカスタムの挙動を定義できる
- custommsg: 純正のノードが処理できないTypeのWireメッセージを受信した際の挙動を定義できる
- htlc_accepted: HTLCで資金を受け取った際の挙動を定義できる

例えばhtlc_acceptedを使ってc-lightning自体にはないHODL invoiceの機能を追加したり(自分が発行したインボイスに関して着金したあと、すぐに解決せず任意のタイミングで解決する)、custommsgを定義することでそのPluginを導入したc-lightning同士で他のプロトコルをしゃべることができます(DLCやtbDEXを利用する際の通信なども実装できる！)。

#### その他
Plugin自体は好きな言語で書けてしまうので、上記の機能を取り入れて自由な処理を記述することができます。VPSを立てて貸し出すもよし、画像を生成するもよし、植木に水やりするもよし。アプリケーション層そのものになるのはよくないと思うので設計は工夫しましょう。
プロジェクト内にPlugin用のフォルダを作って、c-lightningの起動時にそのフォルダからプラグインを読むように指定し、プラグインからはプロジェクト本体の関数を呼び出すのが簡単でいい整理方法だと思いますが、もっといい方法があればぜひ教えてください。

ちなみにLNDでもgRPC APIを使ってイベントの通知を受け取ったりノードを操作することはできます。それでもHooksのようにノードの動作自体を改造することはできないです。

### 今回作るplugin

今回はピアに接続したときに"Hello <peer nodeID>"とコンソールに出力するプラグインをJavascript/Nodeで実装してみましょう。まずはライブラリのインストールを済ませます：

```
$ npm init
$ npm i clightningjs --save
```
コードは下記の通り作ります。

```plugins/test/index.js
const fs = require('fs');
const Plugin = require('clightningjs');

const listener = new Plugin();

listener.onInit = params => {
  listener.log('Initialized!');
}
listener.subscribe('connect');
listener.notifications.connect.on('connect', (params) => {
  listener.log(`Hello ${params.id}`);
});

listener.start();
```

c-lightningは起動時にPluginsフォルダに入ったプラグインを自動的に読み込みますが、コマンドラインオプションを使って別の場所にあるプラグインを明示的に読み込ませることもできます。

さて試してみましょう。bitcoindにつながったc-lightningを用意し、起動オプションでPluginを読み込むようにします。

```
$ lightningd --plugin /path/to/plugins/test/index.js
```

ちなみに、bitcoindがないという人はSauronという別のプラグインを使ってbitcoindの代わりにブロックエクスプローラを使うこともできます。[こちら](https://github.com/lightningd/plugins)からダウンロードしてきた中にあるsauron/sauron.pyを呼び出します：

```
$ lightningd --mainnet --disable-plugin bcli --plugin /path/to/plugins-master/sauron/sauron.py --sauron-api-endpoint https://blockstream.info/api --plugin /path/to/plugins/test/index.js
```

別のターミナルを開きます。今の所チャネルがないノードなので手動でピアに接続する必要があります。1MLやAmbossで適当なピアを探してきて`lightning-cli connect <pubkey@ip:port>`すると、lightningdの出力に…

```
2021-12-13T09:40:34.988Z INFO    lightningd: --------------------------------------------------
2021-12-13T09:40:34.988Z INFO    lightningd: Server started with public key 03030290a6052d26fb9e09145e902035d05d8dd3ddb05dbe3b4f27dcd3a2521594, alias ORANGEAUTO (color #030302) and lightningd 0.10.2
2021-12-13T09:40:34.995Z INFO    plugin-index.js: Initialized!
2021-12-13T09:40:43.491Z INFO    plugin-index.js: Hello 035e4ff418fc8b5554c5d9eea66396c227bd429a3251c8cbc711002ba215bfc226
```

手動でつないだノードの公開鍵が表示されたはずです。ちなみにSauronを使った場合はその上に目玉のような出力が出ていると思います。

ぜひいろんなプラグインを作って遊んでみてください！