【 BOLT】lndディープダイブ ~OpenChannel編~

#はじめに

!注意：執筆途中

lndで送金を行うチュートリアルについて以前記事を書いた。コマンドを叩けばlighnintgで送金をすることができた。しかし各コマンドが裏で何をしていることがのか、などはよくわからなかった。なので今回はlndのコードリーディングをしながら何が起きているのか

lndは読むにはBOLTを理解しているのがかなり大事みたいです。

#BOLTとは 
 Lightning Network仕様の一つ。lndはBOLTに乗っ取って作られている。



#全体の流れ

全体の流れはLND Developer SiteのDockerチュートリアルとほぼ同じ流れで進める。

チャンネル開設 -> invoice発行 -> 送金 -> チャンネルクローズ

登場人物：アリス、ボブ

これらの処理に関係するコードを掻い摘んで行く。



#共通事項
lndの操作を行う時、`lnd`もしくは`lncli`コマンドを入力します。各コマンドは[lnd/cmd](https://github.com/lightningnetwork/lnd/tree/master/cmd)の配下で[urfave/cli](https://github.com/urfave/cli)というパッケージを使って実装されています。
[lnd/server.go]がlndのサーバーで、`lnd`コマンドを入力した時に立ち上がるサーバーの機能を実装しています。
`lncli`コマンドを入力すると、引数に従って各種gRPCメソッドが実行されます。`lncli`で呼び出されるgRPCの処理は[lnd/rpcserver.go](https://github.com/lightningnetwork/lnd/blob/master/rpcserver.go)で実装されています。
`server.go`と`rpcserver.go`の違いは、serverとしての機能と、RPCの処理を別ファイルに別々のファイルで実装するためです。


#チャンネル開設に関係するソース
・`lnd/fundingmanager.go` … チャンネル開設のための各種メソッドが実装されています。
https://github.com/lightningnetwork/lnd/blob/master/fundingmanager.go

・`lnd/peer.go` … lndではpeersに設定されたnodeか仲介者nodeのどちらかに対してchannel開設を行えます。`peer.go`ではメッセージの送受信をし、switchで条件判断しています。

・`/lnd/chanacceptor` … open_channelメッセージを受け取る側の処理に関するコードがまとまったディレクトリ。
https://github.com/lightningnetwork/lnd/tree/master/chanacceptor


・`/lnd/lnwire` … ノード間でやり取りされる各種メッセージのフォーマットを定義している。

・`/lnd/channeldb` … オープン、クローズしたチャンネルや、invoice・paymentに関する記録を[boltdb](https://github.com/etcd-io/bbolt)に記録するためのスクリプトがまとまったディレクトリ。
https://github.com/lightningnetwork/lnd/tree/master/channeldb

#1.チャンネル開設

```
$ lncli openchannel --node_key=<BOB_PUBKEY> --local_amt=1000000
```

アリスがボブとチャンネルを開設する時に入力するコマンドです。

チャンネルを開設する時の処理は[BOLT 02](https://github.com/takaya-imai/lightning-rfc-japanese/blob/master_japanese/02-peer-protocol.md)に書いてあります。

チャンネル開設時、アリスとボブの間では以下のように5種類の6つのメッセージがやり取りされます。

<img width="495" alt="68747470733a2f2f71696974612d696d6167652d73746f72652e73332e61702d6e6f727468656173742d312e616d617a6f6e6177732e636f6d2f302f3438313438382f66663332396363312d353964662d313430642d656364622d3131656662656636633631392e706e67.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/481488/fe629e59-8a4b-2b39-33bd-59262e78d8cc.png">


それぞれのメッセージについてlndのソースコードと共に解説する。

**備考** : BOLTのメッセージのフォーマットはtypeとpayload(data)の二つにより構成されている。
・type … メッセージの種類を示す。
・payload … メッセージからtypeを取り除いた残りの部分。メッセージによって様々なdataに分けられる。

### open_channelメッセージ
lndのopen_chacnnelメッセージのフォーマットを定義しているソースコード↓
[https://github.com/lightningnetwork/lnd/blob/master/lnwire/open_channel.go](https://github.com/lightningnetwork/lnd/blob/master/lnwire/open_channel.go)

open_channelメッセージは以下のような構成になっています。

1.type: 32 (open_channel)
2.data:
・chain_hash(32byte)
　　オープンしたチャネルがあるブロックチェーンのジェネシスブロックのハッシュ値。
・temporary_channel_id(32)
　　ファンディングトランザクションが確立するまでにチャンネルを指定するID。
・funding_satoshis(8)
　　チャンネルをオープンした際チャンネルに置いた金額。
・push_msat(8)
・dust_limit_satoshis(8) 
　　このノードのコミットメントまたはHTLCトランザクションに対して生成されるアウトプットが持つ金額下限値。例えば、この金額とHTLCトランザクション手数料の和よりも小さいHTLCはオンチェーンに入ることができない。 
・max_htlc_value_in_flight_msat(8)
　　未払いHTLCの総和に対する上限値。未払いHTLCに拠出できる金額を制限している、
・channel_reserve_satoshis(8)
　　相手のノードが自身に必ずキープしておかなければいけない最小金額。
・htlc_minimum_msat(4)
　　このノードが受け取れる最も小さいHTLC金額
・feerate_per_kw(4)
　　初期手数料の金額
・to_self_delay(2)
　　相手ノード自身へのアウトプットをどれくらい遅延させておかないといけないかを指定するブロック数。相手のノードがダウンした時に自分の資産を回収するのに待たないといけないblock数。
・max_accepted_htlcs(2)
　　相手のノードが要求できる未払いHTLCの上限数
・funding_pubkey(33) 
　　ファンディングトランザクションアウトプットの2-of-2マルチシグscriptにある公開鍵。
・revocation_basepoint(33)
　　取り消しpreimageと紐づくもので、このコミットメントトランザクションがこのコミットメントトランザクションに対する一意な取り消し鍵を生成する基準になる
・payment_basepoint(33)
　　同様にこのノードへの支払いに使う鍵の列を生成 するために使われる
・delayed_payment_basepoint(33)
　　同様にこのノードへの支払いに使う鍵の列を生成 するために使われる
・first_per_commitment_point(33)　
　

### accept_channelメッセージ

1.type: 33 (accept_channel)
2.data:
・temporary_channel_id(32) 

・dust_limit_satoshis(8) 
・max_htlc_value_in_flight_msat(8)     …　
・channel_reserve_satoshis(8)     …　
・minimum_depth(4)     …
・htlc_minimum_msat(4)     …
・to_self_delay(2)     
・max_accepted_htlcs(2)     …　
・funding_pubkey(33)     …　
・revocation_basepoint(33)    …　
・payment_basepoint(33)     …　
・delayed_payment_basepoint(33)     …　
・first_per_commitment_point(33)     …　




### funding_createメッセージ
1.type: 34 (funding_created)
2.data:
・temporary_channel_id(32)
・funding_txid(32)
・funding_output_index(2)
・signature(64)


### funding_signedメッセージ

fundingに対して最初のcommitment txに必要な署名の情報を渡すためのもの。
1.type: 35 (funding_signed)
2.data:
・channel_id(32)
・signature(64)


### funding_lockedメッセージ




# Channel Open

*TODO:関数遷移図*


`licli openchannel`コマンドを入力すると`server.go`の[OpenChannel()](https://github.com/lightningnetwork/lnd/blob/843b974d66eb08245fe4d1c3b969b817d9a23703/server.go#L3238)が実行されます。


gRPCでは`.proto`でinterfaceを定義し、コマンドを入力するとxxx.pb.goというinterfaceがライブラリとして自動で生成されます。server.goはそれらをimportし、各種interfaceをサーバー・クライアントで実装しています。
ちなみに
[lnd/lnrpc/REDEME.md](https://github.com/lightningnetwork/lnd/blob/843b974d66eb08245fe4d1c3b969b817d9a23703/lnrpc/README.md)を読むとlndで利用可能なRPCのinterfaceのリストを確認することができます。



server.goの[3238行目](https://github.com/lightningnetwork/lnd/blob/843b974d66eb08245fe4d1c3b969b817d9a23703/server.go#L3238)に`OpenChannel()`があります。

```lnd/server.go
func (s *server) OpenChannel(
	req *openChanReq) (chan *lnrpc.OpenStatusUpdate, chan error) {

	// The updateChan will have a buffer of 2, since we expect a ChanPending
	// + a ChanOpen update, and we want to make sure the funding process is
	// not blocked if the caller is not reading the updates.
	req.updates = make(chan *lnrpc.OpenStatusUpdate, 2)
	req.err = make(chan error, 1)

	// First attempt to locate the target peer to open a channel with, if
	// we're unable to locate the peer then this request will fail.
	pubKeyBytes := req.targetPubkey.SerializeCompressed()
	s.mu.RLock()
	peer, ok := s.peersByPub[string(pubKeyBytes)]
	if !ok {
		s.mu.RUnlock()

		req.err <- fmt.Errorf("peer %x is not online", pubKeyBytes)
		return req.updates, req.err
	}
	s.mu.RUnlock()

	// We'll wait until the peer is active before beginning the channel
	// opening process.
	select {
	case <-peer.activeSignal:
	case <-peer.quit:
		req.err <- fmt.Errorf("peer %x disconnected", pubKeyBytes)
		return req.updates, req.err
	case <-s.quit:
		req.err <- ErrServerShuttingDown
		return req.updates, req.err
	}

	// If the fee rate wasn't specified, then we'll use a default
	// confirmation target.
	if req.fundingFeePerKw == 0 {
		estimator := s.cc.feeEstimator
		feeRate, err := estimator.EstimateFeePerKW(6)
		if err != nil {
			req.err <- err
			return req.updates, req.err
		}
		req.fundingFeePerKw = feeRate
	}

	// Spawn a goroutine to send the funding workflow request to the funding
	// manager. This allows the server to continue handling queries instead
	// of blocking on this request which is exported as a synchronous
	// request to the outside world.
	go s.fundingMgr.initFundingWorkflow(peer, req)

	return req.updates, req.err
}
```

最後の方に` go s.fundingMgr.initFundingWorkflow(peer, req)`とあります。ここで`lnd/fundingmanager.go`の[2776行目](https://github.com/lightningnetwork/lnd/blob/master/fundingmanager.go#L2766)
に`initFundingWorkflow()`があります。

```fundingmanager.go
func (f *fundingManager) initFundingWorkflow(peer lnpeer.Peer, req *openChanReq) {
	f.fundingRequests <- &initFundingMsg{
		peer:        peer,
		openChanReq: req,
	}
}
```

これはfundingメッセージの初期化を行なっています。



lndのサーバーが立ち上がった時、毎回`lnd/server.go`の`Start()`が呼び出されます。これはデータベースにあるchannelやpeerの確認をしたり、いらないデータの初期化をしたりするためなのですが、この時、`fundingmanager.go`の[469行目](https://github.com/lightningnetwork/lnd/blob/843b974d66eb08245fe4d1c3b969b817d9a23703/fundingmanager.go#L496)で`start()`が呼び出されます。これがサーバーがリスタートした時に、過去の待機中のfunding処理がデータベースに残っていないかを確認します。確認が終わったら`reservationCoordinator()`を呼び出します。



`fundingmanager.go`の[735行目](https://github.com/lightningnetwork/lnd/blob/843b974d66eb08245fe4d1c3b969b817d9a23703/fundingmanager.go#L735)に`reservationCoordinator()`があります

```lnd/fundingmanager.go
func (f *fundingManager) reservationCoordinator() {
	defer f.wg.Done()

	zombieSweepTicker := time.NewTicker(f.cfg.ZombieSweeperInterval)
	defer zombieSweepTicker.Stop()

	for {
		select {

		case msg := <-f.fundingMsgs:
			switch fmsg := msg.(type) {
			case *fundingOpenMsg:
				f.handleFundingOpen(fmsg)
			case *fundingAcceptMsg:
				f.handleFundingAccept(fmsg)
			case *fundingCreatedMsg:
				f.handleFundingCreated(fmsg)
			case *fundingSignedMsg:
				f.handleFundingSigned(fmsg)
			case *fundingLockedMsg:
				f.wg.Add(1)
				go f.handleFundingLocked(fmsg)
			case *fundingErrorMsg:
				f.handleErrorMsg(fmsg)
			}
		case req := <-f.fundingRequests:
			f.handleInitFundingMsg(req)

		case <-zombieSweepTicker.C:
			f.pruneZombieReservations()

		case req := <-f.queries:
			switch msg := req.(type) {
			case *pendingChansReq:
				f.handlePendingChannels(msg)
			}
		case <-f.quit:
			return
		}
	}
}
```

reservationCoordinator()とはgoの並行処理を用いて、メッセージの内容を判定し、正しいhandlerに受け渡します。

まず最初に`f.handleFundingOpen(fmsg)`が実行されます。

handleFundingOpen()は[1099行目に](https://github.com/lightningnetwork/lnd/blob/843b974d66eb08245fe4d1c3b969b817d9a23703/fundingmanager.go#L1099)あります。

はいくつものif分が並び、walletがチャンネルを開設できる状況かを判断しています。
[1187行目](https://github.com/lightningnetwork/lnd/blob/843b974d66eb08245fe4d1c3b969b817d9a23703/fundingmanager.go#L1187)でついに`lnd/chanacceptor/interface.go`のChannelAcceptRequest()を呼び出し、さらにその中で`lnd/lnwire/open_channel.go`でメッセージのフォーマッティングを行っています。

```lnd/fundingmanager.go
chanReq := &chanacceptor.ChannelAcceptRequest{ 
        Node:        fmsg.peer.IdentityKey(), 
        OpenChanMsg: fmsg.msg, 
} 
```


```lnd/chanacceptor/interface.go

type ChannelAcceptRequest struct {
	// Node is the public key of the node requesting to open a channel.
	Node *btcec.PublicKey

	// OpenChanMsg is the actual OpenChannel protocol message that the peer
	// sent to us.
	OpenChanMsg *lnwire.OpenChannel
}

```

↓`lnd/lnwire/open_channel.go`
https://github.com/lightningnetwork/lnd/blob/master/lnwire/open_channel.go

[1390行目](https://github.com/lightningnetwork/lnd/blob/843b974d66eb08245fe4d1c3b969b817d9a23703/fundingmanager.go#L1390)にて`lnd/peer.go`

```fundingmanager.go
	if err := fmsg.peer.SendMessage(false, fundingSigned); err != nil {
		fndgLog.Errorf("unable to send FundingSigned message: %v", err)
		f.failFundingFlow(fmsg.peer, pendingChanID, err)
		deleteFromDatabase()
		return
	}
```

※go言語に詳しくないのですがif分の`:=`部分が条件分岐の前処理で、そこでSendMessageをこなっています。
SendMessage()を辿っていくと`newMsgStream()`に行き着きます。



　メッセージの送信側である**notifier(通知者)**の側は確認できました。
では今度は**accepter(受信者)**側を見ていきます。
　[1002行目](https://github.com/lightningnetwork/lnd/blob/843b974d66eb08245fe4d1c3b969b817d9a23703/peer.go#L1002)に`readHandler()`という関数があります。これはGoの並行処理で常に走っており、メッセージの受信を行ってます。

readHandler()内の[1074行目](https://github.com/lightningnetwork/lnd/blob/843b974d66eb08245fe4d1c3b969b817d9a23703/peer.go#L1074)からswitchが始ままります。これで受信したメッセージの仕分けを行います。

```lnd/peer.go
	switch msg := nextMsg.(type) {
		case *lnwire.Pong:
			// When we receive a Pong message in response to our
			// last ping message, we'll use the time in which we
			// sent the ping message to measure a rough estimate of
			// round trip time.
			pingSendTime := atomic.LoadInt64(&p.pingLastSend)
			delay := (time.Now().UnixNano() - pingSendTime) / 1000
			atomic.StoreInt64(&p.pingTime, delay)

		case *lnwire.Ping:
			pongBytes := make([]byte, msg.NumPongBytes)
			p.queueMsg(lnwire.NewPong(pongBytes), nil)

		case *lnwire.OpenChannel:
			p.server.fundingMgr.processFundingOpen(msg, p)
		case *lnwire.AcceptChannel:
			p.server.fundingMgr.processFundingAccept(msg, p)
		case *lnwire.FundingCreated:
			p.server.fundingMgr.processFundingCreated(msg, p)
		case *lnwire.FundingSigned:
			p.server.fundingMgr.processFundingSigned(msg, p)
		case *lnwire.FundingLocked:
			p.server.fundingMgr.processFundingLocked(msg, p)

		case *lnwire.Shutdown:
			select {
			case p.chanCloseMsgs <- &closeMsg{msg.ChannelID, msg}:
			case <-p.quit:
				break out
			}
		case *lnwire.ClosingSigned:
			select {
			case p.chanCloseMsgs <- &closeMsg{msg.ChannelID, msg}:
			case <-p.quit:
				break out
			}

		case *lnwire.Error:
			targetChan = msg.ChanID
			isLinkUpdate = p.handleError(msg)

		case *lnwire.ChannelReestablish:
			targetChan = msg.ChanID
			isLinkUpdate = p.isActiveChannel(targetChan)

			// If we failed to find the link in question, and the
			// message received was a channel sync message, then
			// this might be a peer trying to resync closed channel.
			// In this case we'll try to resend our last channel
			// sync message, such that the peer can recover funds
			// from the closed channel.
			if !isLinkUpdate {
				err := p.resendChanSyncMsg(targetChan)
				if err != nil {
					// TODO(halseth): send error to peer?
					peerLog.Errorf("resend failed: %v",
						err)
				}
			}

		case LinkUpdater:
			targetChan = msg.TargetChanID()
			isLinkUpdate = p.isActiveChannel(targetChan)

		case *lnwire.ChannelUpdate,
			*lnwire.ChannelAnnouncement,
			*lnwire.NodeAnnouncement,
			*lnwire.AnnounceSignatures,
			*lnwire.GossipTimestampRange,
			*lnwire.QueryShortChanIDs,
			*lnwire.QueryChannelRange,
			*lnwire.ReplyChannelRange,
			*lnwire.ReplyShortChanIDsEnd:

			discStream.AddMsg(msg)

		default:
			peerLog.Errorf("unknown message %v received from peer "+
				"%v", uint16(msg.MsgType()), p)
		}
```

今受け取ったメッセージはOpenChannelメッセージなのでそのnextMsg(次のメッセージ)である`processFundingAccept()`を呼び出します。fundingmanager.goの[1399行目](https://github.com/lightningnetwork/lnd/blob/843b974d66eb08245fe4d1c3b969b817d9a23703/fundingmanager.go#L1399)に


```lnd/fundingmanager.go
func (f *fundingManager) processFundingAccept(msg *lnwire.AcceptChannel,
	peer lnpeer.Peer) {

	select {
	case f.fundingMsgs <- &fundingAcceptMsg{msg, peer}:
	case <-f.quit:
		return
	}
}
```

ここでlnwireのAcceptChannelを呼び出しメッセージをフォーマッティングし送信します。
通知者側はAcceptChannelのnextMsgである`processFundingCreated()`呼び出されます。


さらに並行処理で常に走っているreservationCoordinator()が受け取ったメッセージを判断し、`handleFundingCreated()`を呼び出します。
`handleFundingCreated()`はfundingmanager.goの[735行目](https://github.com/lightningnetwork/lnd/blob/843b974d66eb08245fe4d1c3b969b817d9a23703/fundingmanager.go#L735)です。

```lnd/fundingmanager.go
func (f *fundingManager) handleFundingCreated(fmsg *fundingCreatedMsg) {
	peerKey := fmsg.peer.IdentityKey()
	pendingChanID := fmsg.msg.PendingChannelID

	resCtx, err := f.getReservationCtx(peerKey, pendingChanID)
	if err != nil {
		fndgLog.Warnf("can't find reservation (peerID:%v, chanID:%x)",
			peerKey, pendingChanID[:])
		return
	}
```







funding_signedメッセージは上にも書いた通り最初のコミットメントトランザクションに必要な署名を与えます。funding_signedメッセージは作成時、ファンディングノードが資金を取り戻せるようなトランザクションをブロードキャストする処理があります。
`handleFundingSigned()`が呼び出されます。


#終わりに
今回のコードリィーディングは開設からクローズに関連する部分だけを掻い摘んだのでいずれ精読してそれ以外の部分も把握したい。