# LN SendCustomMessage Document
`SendCustomMessage`についてのドキュメント
**Version: v0.0.1**
## Setup
### Install [btcd](https://github.com/btcsuite/btcd)
```shell
git clone https://github.com/btcsuite/btcd $GOPATH/src/github.com/btcsuite/btcd
cd $GOPATH/src/github.com/btcsuite/btcd
GO111MODULE=on go install -v . ./cmd/...
```
### Install [lnd](https://github.com/lightningnetwork/lnd)
```shell
git@github.com:lightningnetwork/lnd.git
# compile tagging signrpc routerrpc
make tags="signrpc routerrpc"
make install tags="signrpc routerrpc"
```
### Open Channel
```
# check path and env
export GOPATH=~/YOUR_GO_DIRECTORY
export PATH=$PATH:$GOPATH/bin
export BTCD_DATA_PATH=YOUR_BTCD_DATA_PATH
# run btcd
btcd --txindex --simnet --rpcuser=kek --rpcpass=kek --datadir="BTCD_DATA_PATH"
# create workspaces
cd $GOPATH
mkdir dev
cd dev
mkdir alice bob
# alice
cd $GOPATH/dev/alice
lnd --rpclisten=localhost:10001 --listen=localhost:10011 --restlisten=localhost:8001 --datadir=data --logdir=log --debuglevel=info --bitcoin.simnet --bitcoin.active --bitcoin.node=btcd --btcd.rpcuser=kek --btcd.rpcpass=kek --routerrpc.attemptcost=0 --accept-keysend
lncli --rpcserver=localhost:10001 --macaroonpath="$GOPATH"/dev/alice/data/chain/bitcoin/simnet/admin.macaroon create
alias lncli-alice="lncli --rpcserver=localhost:10001 --macaroonpath= --macaroonpath=$GOPATH/dev/alice/data/chain/bitcoin/simnet/admin.macaroon"
lncli-alice newaddress np2wkh
# bob
cd $GOPATH/dev/bob
lnd --rpclisten=localhost:10002 --listen=localhost:10012 --restlisten=localhost:8002 --datadir=data --logdir=log --debuglevel=info --bitcoin.simnet --bitcoin.active --bitcoin.node=btcd --btcd.rpcuser=kek --btcd.rpcpass=kek --routerrpc.attemptcost=0 --accept-keysend
lncli --rpcserver=localhost:10002 --macaroonpath="$GOPATH"/dev/bob/data/chain/bitcoin/simnet/admin.macaroon create
alias lncli-bob="lncli --rpcserver=localhost:10002 --macaroonpath= --macaroonpath=$GOPATH/dev/bob/data/chain/bitcoin/simnet/admin.macaroon"
lncli-bob newaddress np2wkh
# get BTC
# stop your btcd and run following command
btcd --txindex --simnet --rpcuser=kek --rpcpass=kek --datadir="$BTCD_DATA_PATH" --miningaddr=<ALICE_ADDRESS>
btcctl --simnet --rpcuser=kek --rpcpass=kek generate 400 -C "$BTCD_DATA_PATH"
cd $GOPATH/dev/alice
lncli-alice walletbalance
# create p2p network
# get bob pubkey
lncli-bob getinfo
lncli-alice connect <BOB_PUBKEY>@localhost:10012
lncli-alice listpeers
# open channel
lncli-alice openchannel --node_key=<BOB_PUBKEY> --local_amt=1000000
btcctl --simnet --rpcuser=kek --rpcpass=kek generate 6 -C "$BTCD_DATA_PATH"
lncli-alice listchannels
# pay
lncli-bob addinvoice --amt=10000
lncli-alice sendpayment --pay_req=<INVOICE>
lncli-alice listchannels
```
## SendCustomMessageについて
- インボイスなしにピアのノードに、メッセージを送信することができる。
- **公開鍵で指定されるノードは、ピアである必要がある。ノードと直接接続されている必要がある。**
- 受信側はメッセージをサブスクライブする必要がある。
- メッセージの形式: 16進数文字列にエンコードする必要がある。
- データの最大サイズ: `65533` 下記参照。
- LND versionのminimumバージョンの指定がある。
### SendCustomMessageのインターフェイス
```javascript
/* lncli: `sendcustom`
SendCustomMessage sends a custom peer message.
*/
rpc SendCustomMessage (SendCustomMessageRequest)
returns (SendCustomMessageResponse);
/* lncli: `subscribecustom`
SubscribeCustomMessages subscribes to a stream of incoming custom peer
messages.
*/
rpc SubscribeCustomMessages (SubscribeCustomMessagesRequest)
returns (stream CustomMessage);
}
message CustomMessage {
// Peer from which the message originates
bytes peer = 1;
// Message type. This value will be in the custom range (>= 32768).
uint32 type = 2;
// Raw message data
bytes data = 3;
}
message SendCustomMessageRequest {
// Peer to send the message to
bytes peer = 1;xxx
// Message type. This value needs to be in the custom range (>= 32768).
uint32 type = 2;
// Raw message data.
bytes data = 3;
}
```
### インターフェイスの例
送信メッセージは、16進数でエンコードする必要がある。または、typeは、`32768`から`65533`の間で指定する。
```javascript
// SendMessage
{
message: Buffer.from(message).toString("hex"),
public_key: '021c5096cb9a5c6b150b6fae93d0026c90482de3393c7f88d8b9c0cd11708a6cd3'
type: 32768,
}
// ResponseMessage
{
message: '68656c6c6f20776f726c64', // need to be decoded
public_key: '021c5096cb9a5c6b150b6fae93d0026c90482de3393c7f88d8b9c0cd11708a6cd3',
type: 32768
}
```
### メッセージのデータ量の制限
メッセージデータは、最大`65533`までで、データサイズの制限を超過すると下記エラーが出る。
```
// error message
message payload is too large - encoded 81920 bytes, but maximum message payload is 65533 bytes
```
### 試す
**ピア間でのメッセージ送信**
条件: AliceとBob、BobとCharlieはそれぞれPeerで直接繋がっているが、AliceとCharlieはPeerで繋がっていない。
結果: AliceとBob間、BobとCharlie間のメッセージ送受信を行うことはできるが、AliceとCharilie間のメッセージの送受信を行うことはできない。
```
(1) (1) (1)
+ ----- + + --- + + ------- +
| Alice | <---- peer ----> | Bob | <---- peer ----> | Charlie |
+ ----- + + --- + + ------- +
| | |
| | |
+ <- can send message - -> + <- can send message - -> +
| | |
| | |
+ <- - - -- - - - cannot send message - - - -- - - -> +
```
AliceからCharilieへメッセージを送った際のエラーメッセージ
```
err: Error: 5 NOT_FOUND: peer is not connected // Alice peers don't have Charilie.
```
**ピア間での疑似ブロードキャスト送信**
条件: AliceとBob、BobとCharlieはそれぞれPeerで直接繋がっているが、AliceとCharlieはPeerで繋がっていない。
結果: AliceとBobにメッセージを送信後に、メッセージイベントをサブスクライブしたBobがCharlieにメッセージを送信することができる。
```
(1) (1) (1)
+ ----- + + --- + + ------- +
| Alice | <---- peer ----> | Bob | <---- peer ----> | Charlie |
+ ----- + + --- + + ------- +
| | |
| | |
+ <- can send message - -> + <- can send message - -> +
| | |
| | |
+ <- - - -- - - - can send message - - - - - - - - -> +
```
### 簡易的な実装
```javascript
import {
AuthenticatedLnd,
LndAuthenticationWithMacaroon,
authenticatedLndGrpc,
getWalletInfo,
getPeers as getLightningPeers,
GetPeersResult,
sendMessageToPeer,
subscribeToPeerMessages,
LightningMessage,
} from "lightning";
import { config } from "../config";
import { ReceivedMessage } from "../interfaces";
import { logger } from "../lib/logger";
import { accessDatabase } from '../lib/database';
const { lnd: lndConfig } = config;
class LndProvider {
public _name: string;
private _lnd: AuthenticatedLnd;
private _publicKey: string;
public _messages: ReceivedMessage[];
constructor(name: string, socket: string, macaroon: string) {
this._name = name;
try {
const { lnd } = authenticatedLndGrpc({
cert: lndConfig.tlsCert,
macaroon,
socket,
} as LndAuthenticationWithMacaroon);
this._lnd = lnd;
} catch (error) {
const { message } = <Error>error;
logger.error(`cannot initialize lnd ${message}`);
}
}
async getPublicKey(): Promise<string> {
if (this._publicKey) return this._publicKey;
const publicKey = (await getWalletInfo({ lnd: this._lnd })).public_key;
this._publicKey = publicKey;
return publicKey;
}
async getPeers(): Promise<GetPeersResult> {
return await getLightningPeers({ lnd: this._lnd });
}
// Peer means be just visible target.
sendMessage(public_key: string, message: string): void {
try {
sendMessageToPeer(
{
lnd: this._lnd,
message: Buffer.from(message).toString("hex"),
public_key,
},
(res) => {
// you don't have any notification from subscriber
console.log("res", res);
},
);
} catch (error) {
const { message } = <Error>error;
logger.error(`cannot send message ${message}`);
}
}
/*
export type LightningMessage = {
// Message Hex String
message: string;
// To Peer Public Key Hex String
public_key: string;
// Message Type Number
type?: number;
};
*/
subscribeMessage(): void {
const sub = subscribeToPeerMessages({ lnd: this._lnd });
sub.on("message_received", async (received: LightningMessage): Promise<void> => {
console.log(`${this._name} received message`, received);
try {
const rawMsg = Buffer.from(received.message, "hex").toString();
const parsed = JSON.parse(rawMsg) as ReceivedMessage;
console.log("parsed message is ", parsed);
await accessDatabase();
if (parsed.broadcast) {
await this.broadcastMessage(received, rawMsg);
}
this._messages.push(parsed)
} catch (error) {
console.error(error);
}
});
}
async broadcastMessage(received: LightningMessage, rawMsg: string): Promise<void> {
try {
const peers = await this.getPeers();
const filterPeers = peers.peers.filter((peer) => peer.public_key != received.public_key);
filterPeers.map((peer) => {
this.sendMessage(peer.public_key, rawMsg);
});
} catch (error) {
console.error(error);
}
}
}
export default LndProvider;
```
### Bash
```shell
docker run -it -v "$PWD":/usr/local/ --rm busybox
base64 -w0 /usr/local/tls.cert
base64 -w0 /usr/local/admin.macaroon
```
### 参考資料
- [lightning.proto](https://github.com/lightningnetwork/lnd/blob/master/lnrpc/lightning.proto#L561)
- [c-lightning sencustommsg](https://lightning.readthedocs.io/lightning-sendcustommsg.7.html)
- [lncli/cmd_custom](https://github.com/lightningnetwork/lnd/blob/e6c65f1cbd5a7f3f1aed7b5ef7720a7112156d78/cmd/lncli/cmd_custom.go)
## Umbrel
```bash
# get public key
docker exec -it lnd lincli getinfo
# error lightninglabs/lnd:v0.13.3-betaだとサポートエラーになる
SendMessageToPeerMethodNotSupported
```
## TODO
- peerが切れたときの対応を行う。
- 外部ipを開放する必要がある。
- umbrelの0.13.3-betaだと駄目。