Skip to content

Instantly share code, notes, and snippets.

@carlaKC
Last active July 24, 2019 21:27
Show Gist options
  • Save carlaKC/cb10ad95554b1f7ace0b9b61c21d226d to your computer and use it in GitHub Desktop.
Save carlaKC/cb10ad95554b1f7ace0b9b61c21d226d to your computer and use it in GitHub Desktop.
Test LND gRPC Connection Weirdness

Background

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.

Install

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}

Test Program

// 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
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment