I used this go snippet to test for some weirdness with using LND's unlocker and lightning grpc serivces with the same client connection.
To my knowledge, using the same connection for two grpc services running on the same port is ok, but I may be totally wrong there.
Wallet password is passed plaintext becasue it was just easier, so make sure to run this for a non-maiinet wallet or make sure it doens't get saved in your bash history :)
You need go installed and a running locked LND node to test this.
Copy the code in the snippet below into $GOPATH/src/checkbug
go install $GOPATH/checkbug/
$GOPATH/bin/checkbug --lnd_address={LN node grpc address} --lnd_cert={path to cert} --lnd_macaroon={macarooon path} --lnd_password={wallet password}
// package main connects to LND's wallet unlocker and rpc service and tests
// for a bug where the lightning service is unavailable after unlocking.
// Your LND node should start in a locked state to test this, and the code
// assumes that you use macaroons (although the macaroon code can be deleted
// without affecting results). If an error other than the expected errors occur,
// the program just fails.
package main
import (
"context"
"encoding/hex"
"flag"
"io/ioutil"
"log"
"strings"
"time"
"github.com/lightningnetwork/lnd/lnrpc"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/metadata"
)
var (
lndAddr = flag.String("lnd_address", "",
"address of LND grpc server")
lndCert = flag.String("lnd_cert", "",
"location of LND tls certificate")
lndWalletPassword = flag.String("lnd_password", "",
"password for your wallet")
lndMacaroon = flag.String("lnd_macaroon", "",
"location of LND macaroon")
)
func main() {
flag.Parse()
cl, err := connect()
if err != nil {
log.Fatalf("Could not connect to LND: %v", err)
}
// append macaroon to context.
ctx := metadata.AppendToOutgoingContext(
context.Background(),
"macaroon",
cl.macaroon,
)
// Unlock wallet - expected to work.
_, err = cl.walletUnlockerClient.UnlockWallet(
ctx,
&lnrpc.UnlockWalletRequest{
WalletPassword: []byte(*lndWalletPassword),
},
)
if err != nil {
// error if unlock fails, LND was probably already unlocked, exit.
log.Fatalf("Error unlocking wallet: %v", err)
} else {
log.Println("Unlocked LND successfully")
}
log.Println("Sleeping for 5 seconds - giving the lightning " +
"service time to start up; just to be super cautious")
time.Sleep(time.Second*5)
// Getinfo - if the bug is present, this call should error with lightning unknown.
_, err = cl.rpcClient.GetInfo(
ctx,
&lnrpc.GetInfoRequest{},
)
if err != nil && strings.Contains(err.Error(), "unknown service lnrpc.Lightning") {
log.Printf("Same connection fails: %v", err)
} else if err != nil {
// something else is wrong, abort
log.Fatalf("Error getting info: %v", err)
}
// Refresh the lightning client with the same connection
cl.rpcClient = lnrpc.NewLightningClient(cl.rpcConn)
_, err = cl.rpcClient.GetInfo(
ctx,
&lnrpc.GetInfoRequest{},
)
if err != nil {
log.Printf("Refresh with same connection does not work, got error: %v", err)
} else {
log.Println("Reconnecting fixed issue")
}
// Refresh entire connection by closing and redialing
cl.Close()
cl, err = connect()
if err != nil {
log.Fatalf("Could not re-connect to LND: %v", err)
}
log.Println("Sleeping for 5 seconds to give the connection time to close. ")
// Wait a bit otherwise you get err transport closing.
time.Sleep(time.Second * 5)
_, err = cl.rpcClient.GetInfo(
ctx,
&lnrpc.GetInfoRequest{},
)
if err != nil {
log.Printf("New connection does not work, got error: %v", err)
} else {
log.Println("New connection fixed issue")
}
}
type Client struct {
macaroon string
lndGRPCAddress string
walletPassword []byte
cert credentials.TransportCredentials
rpcConn *grpc.ClientConn
rpcClient lnrpc.LightningClient
walletUnlockerClient lnrpc.WalletUnlockerClient
}
func (cl *Client) Close() {
err := cl.rpcConn.Close()
// don't fatal here because we're just hard resetting
if err != nil {
log.Printf("Close error: %v", err)
}
}
func connect() (*Client, error) {
cl := &Client{
lndGRPCAddress: *lndAddr,
macaroon: getMacaroon(),
cert: getCert(),
}
conn, err := grpc.Dial(cl.lndGRPCAddress,
grpc.WithTransportCredentials(cl.cert))
if err != nil {
return nil, err
}
cl.rpcConn = conn
// create clients from one connection
cl.rpcClient = lnrpc.NewLightningClient(conn)
cl.walletUnlockerClient = lnrpc.NewWalletUnlockerClient(conn)
return cl, nil
}
// get macaroon from path provided, error if empty path.
func getMacaroon() string {
if *lndMacaroon == "" {
log.Fatal("no macaroon path provided")
}
dat, err := ioutil.ReadFile(*lndMacaroon)
if err != nil {
log.Fatalf("cannot read macaroon path: %v", err)
}
return hex.EncodeToString(dat)
}
// getCert from path provided, error if empty path.
func getCert() credentials.TransportCredentials {
if *lndCert == "" {
log.Fatal("no cert path provided")
}
cert, err := credentials.NewClientTLSFromFile(*lndCert, "")
if err != nil {
log.Fatalf("cannot read cert path: %v", err)
}
return cert
}