LNDでonchain fundをリカバリーするには

lightning nodeの運用にdockerを使おうと思っていて、ふと気になった部分。
LNDで提供されているsampleでは、noseedbackupオプションを使っている。

exec lnd \  
    --noseedbackup \
    --logdir="/data" \
    "--$CHAIN.active" \
    "--$CHAIN.$NETWORK" \
    "--$CHAIN.node"="btcd" \
    "--$BACKEND.rpccert"="/rpc/rpc.cert" \
    "--$BACKEND.rpchost"="blockchain" \
    "--$BACKEND.rpcuser"="$RPCUSER" \
    "--$BACKEND.rpcpass"="$RPCPASS" \
    --debuglevel="$DEBUG" \
    "$@"

このオプションをつけているのは、おそらくサンプルアプリレベルでdockerでパスワード入力するのは複雑っていうのもあると思うが、そもそも、Lightningにおけるseed back upってなんだ?っていうところから調べる。

リカバリー

Recovering Funds From lndっていうdocがあった。 Lightning networkでは、オンチェーン、オフチェーンで復旧のパターンがあるが、seed backupの文脈ではオンチェーンのほうなので、そっちを確認する。

On-Chain Recovery

24-word Cipher Seeds

新しいLNDが作られたとき、cipher seedと呼ばれる、24語の単語が与えられる。lncli createを実行したときにCLIで提供されるのは下記のようなものだ。

!!!YOU MUST WRITE DOWN THIS SEED TO BE ABLE TO RESTORE THE WALLET!!!

---------------BEGIN LND CIPHER SEED---------------
 1. ability   2. noise   3. lift     4. document
 5. certain   6. month   7. shoot    8. perfect
 9. matrix   10. mango  11. excess  12. turkey
13. river    14. pitch  15. fluid   16. rack  
17. drill    18. text   19. buddy   20. pool  
21. soul     22. fatal  23. ship    24. jelly  
---------------END LND CIPHER SEED-----------------

!!!YOU MUST WRITE DOWN THIS SEED TO BE ABLE TO RESTORE THE WALLET!!!

Wallet and Seed Passphrases

walletを作成するとき、userはpasswordを入力するように求められる。

Input wallet password:  
Confirm wallet password:  

このパスワードはウォレットを暗号化するために利用され、派生する秘密鍵、公開鍵を作成する。 (cipher seed自体の暗号化も可能だが、今回は省略する)

Starting On-Chain Recovery

オンチェーンに存在する資金のリカバリー手順は、lncli createから始まる。 続いて、新しいwallet passwordを派生キーのために入力する。

Input wallet password:  
Confirm wallet password:  

新しいwallet passwordが入力されると、ユーザは既存のcipher seedの入力を求められる。

Input your 24-word mnemonic separated by spaces: ability noise lift document certain month shoot perfect matrix mango excess turkey river pitch fluid rack drill text buddy pool soul fatal ship jelly  

最後に、ユーザはリカバリーウィンドウのオプションを取得する。

Input an optional address look-ahead used to scan for used keys (default 2500):  

リカバリーウィンドウは、利用済のアドレスをオンチェーンでスキャンし終わったあと、いくつのアドレスを調べるかを決定するのに利用される。例えば、もし2が設定されている場合、lndは連続する2つのアドレスが利用されていなかった時点で、資金の発見に失敗する。
lndのonchain walletが広く利用されるようになったら、この数字を大きくする必要がある。

すべての入力が正しければ、下記のようなpromptが再度表示される。

!!!YOU MUST WRITE DOWN THIS SEED TO BE ABLE TO RESTORE THE WALLET!!!

---------------BEGIN LND CIPHER SEED---------------
 1. ability   2. noise   3. lift     4. document
 5. certain   6. month   7. shoot    8. perfect
 9. matrix   10. mango  11. excess  12. turkey
13. river    14. pitch  15. fluid   16. rack  
17. drill    18. text   19. buddy   20. pool  
21. soul     22. fatal  23. ship    24. jelly  
---------------END LND CIPHER SEED-----------------

!!!YOU MUST WRITE DOWN THIS SEED TO BE ABLE TO RESTORE THE WALLET!!!

lnd successfully initialized!  

lndのログではこのように表示されているはず。

[INF] LNWL: Opened wallet
[INF] LTND: Wallet recovery mode enabled with address lookahead of 2500 addresses
[INF] LNWL: RECOVERY MODE ENABLED -- rescanning for used addresses with recovery_window=2500
[INF] CHBU: Updating backup file at test_lnd3/data/chain/bitcoin/simnet/channel.backup
[INF] CHBU: Swapping old multi backup file from test_lnd3/data/chain/bitcoin/simnet/temp-dont-use.backup to test_lnd3/data/chain/bitcoin/simnet/channel.backup
[INF] LNWL: Seed birthday surpassed, starting recovery of wallet from height=748 hash=3032830c812a4a6ea305d8ead13b52e9e69d6400ff3c997970b6f76fbc770920 with recovery-window=2500
[INF] LNWL: Scanning 1 blocks for recoverable addresses
[INF] LNWL: Recovered addresses from blocks 748-748
[INF] LNWL: Started rescan from block 3032830c812a4a6ea305d8ead13b52e9e69d6400ff3c997970b6f76fbc770920 (height 748) for 800 addresses
[INF] LNWL: Catching up block hashes to height 748, this might take a while
[INF] LNWL: Done catching up block hashes
[INF] LNWL: Finished rescan for 800 addresses (synced to block 3032830c812a4a6ea305d8ead13b52e9e69d6400ff3c997970b6f76fbc770920, height 748)

最後の行がスキャンが完了したことを示している。もしすべての資金が返ってきていない場合、リカバリーウィンドウを増やして再実行する必要がある。

スキャンがすべて終わらない場合(例えばLNDがシャットダウンする)、lncli unlockを使うことで終了時点から再実行をすることが可能。

lncli unlock --recovery_window=2500  

--recovery-windowが存在しない場合、ウォレットはリカバリーモードにならない。Forced In-Place Rescanという機能も存在するようだが、今回は省略する。

k8sでどう運用すべきか

cipher seedが出力される以上、どこかのテキストファイルにそれを保存するのは良策ではないので、手動で行うのがベストだと思う。 lndにある解説も参考になるが、おそらく下記のようなdockerfileが必要になる。

FROM golang:alpine as builder

# Force Go to use the cgo based DNS resolver. This is required to ensure DNS
# queries required to connect to linked containers succeed.
ENV GODEBUG netdns=cgo

# Install dependencies and build the binaries.
RUN apk add --no-cache --update alpine-sdk \  
    git \
    make \
    gcc \
&&  git clone https://github.com/lightningnetwork/lnd /go/src/github.com/lightningnetwork/lnd \
&&  cd /go/src/github.com/lightningnetwork/lnd \
&&  make \
&&  make install tags="signrpc walletrpc chainrpc invoicesrpc routerrpc"

# Start a new, final image.
FROM alpine as final

# Define a root volume for data persistence.
VOLUME /root/.lnd

# Add bash and ca-certs, for quality of life and SSL-related reasons.
RUN apk --no-cache add \  
    bash \
    ca-certificates

# Copy the binaries from the builder image.
COPY --from=builder /go/bin/lncli /bin/  
COPY --from=builder /go/bin/lnd /bin/

# Expose lnd ports (p2p, rpc).
EXPOSE 9735 10009

# Specify the start command and entrypoint as the lnd daemon.
ENTRYPOINT ["lnd"]  
CMD ["lnd"]  

ウォレットの作成自体は手動でやるしかないので、下記のようなコマンドを自分で実行する。 ここでseedが表示されるので、これをメモしておく。

docker exec -it lnd lncli create  

これはもっともシンプルな例であり、btcd等を組み合わせてpodを作成するときはポートの設定等も必要になる。