ビルトインCライトニングプラグイン.ネット


最近、私は修正されましたmy own Lapps 仕事をするc-lightning plugin と一緒に仕事をするためにLND でもc-lightning ( What are Lapps? ).
私は誰かがそれをやって見つけることができなかった.ここでは、方法のガイドラインを残します.
私もa sample application , それで、あなたが説明よりサンプルに興味があるならば、そこへ行ってください.私の申請はF - CUNKですが、Cサンプルで書かれています.言語は異なりますが、基本的なアプローチはどちらの場合も同じです.
この場合、1つのクラスがあると仮定しますMyAwesomeRpcServer , これはRPC呼び出しのメソッドハンドラを持っています.このように始まる

using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using StreamJsonRpc;

namespace HelloWorldPlugin.Server
{
  public class MyAwesomeRpcServerOptions
  {
    public string GreeterName { get; set; } = "World";
    public bool IsInitiated { get; set; }
  }

  public class MyAwesomeRpcServer
  {
    private readonly MyAwesomeRpcServerOptions _opts;

    public MyAwesomeRpcServer(MyAwesomeRpcServerOptions opts)
    {
      _opts = opts;
    }

    [JsonRpcMethod("hello")]
    public async Task<string> HelloAsync(string name)
    {
      return $"hello!! {name}! This is {_opts.GreeterName} !!";
    }
  }
}
要件は
  • こんにちはと呼ばれるRPCメソッドを公開したい.
  • を含むメッセージを返しますGreeterName コマンドライン引数として渡されます.
  • ユーザからRPCパラメータとして渡された名前を含むメッセージを返します.
  • 露光されたrpcの数は今後増加する可能性がある.
  • 1 . StreamJSONRPCを使用してJSONRPCServerを作成します。


    まず、通常のJSONRPC 2.0サーバーとして機能するアプリケーションを作成します.
    これを行う標準的な方法はStreamJsonRpc MSによって書かれる図書館.
    HTTP経由で通常のWebサーバーとして使用する場合は、this sample は良いリファレンスです.
    しかし、Cライトニングのプラグインはlightningd 標準入力/出力によるそれ自体Console.In and Console.Out 輸送用.
    このように見える
    var rpcServer = new MyAwesomeRpcServer();
    var formatter = new JsonMessageFormatter();
    var handler = new NewLineDelimitedMessageHandler(Console.OpenStandardOutput(), Console.OpenStandardInput(), formatter);
    var rpc = new JsonRpc(handler);
    rpc.AddLocalRpcTarget(rpcServer, new JsonRpcTargetOptions());
    rpc.ExceptionStrategy = ExceptionProcessing.CommonErrorData;
    rpc.StartListening();
    
    JSON RPC 2.0仕様では、それぞれのメッセージとトランスポートを分離する方法を指定しません.
    Cライトニングプラグインは、メッセージを区切るために改行を使用するので、我々は使用しますNewLineDelimitedMessageHandler メッセージハンドラとして.
    また、もし通信相手がC≧でない場合(我々の場合ではない)、指定するのは良いことですExceptionStrategy ASExceptionProcessing.CommonErrorData .

    read / write操作スレッドを安全にします。


    通常、RPCサーバはマルチスレッドモードで動作しますが、stdIN/Outputを並列に出力するとメッセージが破損します.
    この問題を解決する最も簡単な方法は、要求を順次処理することです.
    この場合、最も簡単な方法はAsyncSemaphore , ASdescribed in the official guidelines .
    プラグインの場合、RPC呼び出しに関連したパフォーマンスの問題はまれであるので、我々はすべてのメソッドのためにセマフォを獲得することによって、書き込みを制限することに決めました.

    3 .メソッドの自動生成


    Cライトニングプラグインは2つのRPCメソッドを公開しなければなりません:getmanifest and init .
    前者はプラグインからCライトニングへの情報を伝えることであり、後者はCライトニングが開始されたときに情報を迂回することである.
    多くの処理ロジックを自動的に他のプラグインのメソッド定義から生成することができますので、我々は物事を十分に一般的に保つために反射を使用します.
    例えば、getmanifest このように見える
        [JsonRpcMethod("getmanifest")]
        public async Task<ManifestDto> GetManifestAsync(bool allowDeprecatedApis = false, object? otherParams = null)
        {
          using var releaser = await _semaphore.EnterAsync();
          var userDefinedMethodInfo =
            this
              .GetType()
              .GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly)
              .Where(m => !m.IsSpecialName && !String.Equals(m.Name, "initasync", StringComparison.OrdinalIgnoreCase) && !String.Equals(m.Name, "getmanifestasync", StringComparison.OrdinalIgnoreCase));
          var methods =
            userDefinedMethodInfo
              .Select(m =>
              {
                var argSpec = m.GetParameters();
                var numDefaults =
                  argSpec.Count(s => s.HasDefaultValue);
                var keywordArgsStartIndex = argSpec.Length - numDefaults;
                var args =
                  argSpec.Where(s =>
                    !string.Equals(s.Name, "plugin", StringComparison.OrdinalIgnoreCase) &&
                    !string.Equals(s.Name, "request", StringComparison.OrdinalIgnoreCase))
                    .Select((s, i) => i < keywordArgsStartIndex ? s.Name : $"[{s.Name}]");
    
                var name = m.Name.ToLowerInvariant();
                if (name.EndsWith("async"))
                  name = name[..^5];
                return new RPCMethodDTO
                {
                  Name = name,
                  Usage = string.Join(' ', args),
                  Description = _rpcDescriptions[name].Item1,
                  LongDescription = _rpcDescriptions[name].Item2
                };
              });
    
          return new ManifestDto
          {
            Options =
              // translate `System.CommandLine` to c-lightning compatible style.
              CommandLines.GetOptions().Select(CliOptionsToDto).ToArray(),
            RpcMethods = methods.ToArray(),
            Notifications = new NotificationsDTO[]{},
            Subscriptions = new string[]{},
            Hooks = new object[] {},
            Dynamic = true,
            FeatureBits = null
          };
    
    INITは以下の通りである.これはGetManifestよりも自動化するのがより困難です.また、プロセスはアプリケーションの要件によって異なります.
        [JsonRpcMethod("init")]
        public async Task InitAsync(LnInitConfigurationDTO configuration, Dictionary<string, object> options)
        {
          using var releaser = await _semaphore.EnterAsync();
          foreach (var op in options)
          {
            var maybeProp =
              _opts.GetType().GetProperties()
                .FirstOrDefault(p => string.Equals(p.Name, op.Key, StringComparison.OrdinalIgnoreCase));
            maybeProp?.SetValue(_opts, op.Value);
          }
          _opts.IsInitiated = true;
        }
    
    initはメソッド引数をオブジェクトとして渡します.
    オブジェクトはフィールド名configurations and options , したがって、Cの角側のメソッドハンドラは、自動的にバインドするのと同じ名前でなければなりません.
    この場合、私たちは、Cの稲妻側によってスタートアップオプションgivinを結合するだけです_opts .LnInitConfigurationDto 'Cライトニング側の情報(例えばtorを使用しているユーザーですか?どんな稲妻特徴ビットが支えられるか?)を使用して初期化処理を自由に実行できます.
    セットIsInitiated 初期化処理終了.
    これは、initメッセージが受信されるまで、サーバーの起動プロセスを中断するために後で使用されます.

    jsonrpclogger


    Cライトニングとプラグインが標準入出力を介して通信するという事実は、ログを標準出力に送信することはできません.
    既存のCライトニングプラグインはC -ライトニングにメッセージを送りますlog RPC通知としてRPCメソッドと残りの処理をCライトニング側に残します.
    これにより、Cライトニング側でログを統一し、読みやすくする.
    . NETにバンドルされます.ConsoleLogger , しかし、これはバックグラウンドタスクとして書き込みプロセスを実行するので、同じようにロギングロジックを実装するならば、上記のスレッドレースが起こるかもしれません.
    これらの問題を解決するにはI have created my own ILoggerProvider .

    起動処理


    Cライトニングは起動時にユーザが指定したプラグインを起動し、環境変数LIGHTNINGD_PLUGIN=1 起動時.
    このように、この環境変数の有無は、アプリケーションがプラグインとして実行されるべきであるかどうかをプログラムで決定するために使用することができる.
    私の場合、2つの場所でこの変数を参照します
    一つ目はホストの設定です.
    Action<IHostBuilder> configureHostBuilder = hostBuilder =>
    {
      var isPluginMode = Environment.GetEnvironmentVariable("LIGHTNINGD_PLUGIN") == "1";
    
      if (isPluginMode)
      {
        hostBuilder
          .ConfigureLogging(builder => { builder.AddJsonRpcNotificationLogger();})
          .ConfigureServices(serviceCollection =>
            serviceCollection
              .AddSingleton<MyAwesomeRpcServerOptions>()
              .AddSingleton<MyAwesomeRpcServer>()
          );
      }
      else
      {
         // configuration for running as a normal webserver.
      }
    };
    
    もう一つはホストの実行中です.
    の実行を遅らせるIHost.RunAsync() 下記の通り.
      var host = hostBuilder.Build();
      var isPluginMode = Environment.GetEnvironmentVariable("LIGHTNINGD_PLUGIN") == "1";
      if (isPluginMode)
        await host.StartJsonRpcServerForInitAsync(); /// initialize only rpc server and listen to `init` call.
      await host.RunAsync();
    
    これにより起動が遅れるBackgroundService そして、initメッセージがlightningdから受信されるまで、出力されるログ.

    単一のバイナリとしてコンパイルする


    最後に、プラグインを実行可能な単一のバイナリとしてコンパイルする必要があります.
    したがって、我々はSingle file Executable Compilation 特徴.ネット
    そして、それ!
    あなたは--plugin or --plugin-dir Cライトニングのプラグインを起動します.

    図書館として発行する?


    最初は、今回私がしたことをライブラリに変換して公開していますが、それをするのが難しくないので、initプロセスが自動化するのが難しいので、ドキュメントと例をここに残しておきます.
    ライブラリにしたい場合は、おそらく最良の方法はSource Generator