Files
lspd/lnd_client.go
2023-03-24 14:43:20 +01:00

233 lines
6.4 KiB
Go

package main
import (
"context"
"crypto/x509"
"encoding/hex"
"fmt"
"log"
"github.com/breez/lspd/basetypes"
"github.com/breez/lspd/config"
"github.com/breez/lspd/lightning"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/htlcswitch/hop"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnrpc/chainrpc"
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
)
type LndClient struct {
client lnrpc.LightningClient
routerClient routerrpc.RouterClient
chainNotifierClient chainrpc.ChainNotifierClient
conn *grpc.ClientConn
}
func NewLndClient(conf *config.LndConfig) (*LndClient, error) {
_, err := hex.DecodeString(conf.Macaroon)
if err != nil {
return nil, fmt.Errorf("failed to decode macaroon: %w", err)
}
// Creds file to connect to LND gRPC
cp := x509.NewCertPool()
if !cp.AppendCertsFromPEM([]byte(conf.Cert)) {
return nil, fmt.Errorf("credentials: failed to append certificates")
}
creds := credentials.NewClientTLSFromCert(cp, "")
macCred := NewMacaroonCredential(conf.Macaroon)
// Address of an LND instance
conn, err := grpc.Dial(
conf.Address,
grpc.WithTransportCredentials(creds),
grpc.WithPerRPCCredentials(macCred),
)
if err != nil {
log.Fatalf("Failed to connect to LND gRPC: %v", err)
}
client := lnrpc.NewLightningClient(conn)
routerClient := routerrpc.NewRouterClient(conn)
chainNotifierClient := chainrpc.NewChainNotifierClient(conn)
return &LndClient{
client: client,
routerClient: routerClient,
chainNotifierClient: chainNotifierClient,
conn: conn,
}, nil
}
func (c *LndClient) Close() {
c.conn.Close()
}
func (c *LndClient) GetInfo() (*lightning.GetInfoResult, error) {
info, err := c.client.GetInfo(context.Background(), &lnrpc.GetInfoRequest{})
if err != nil {
log.Printf("LND: client.GetInfo() error: %v", err)
return nil, err
}
return &lightning.GetInfoResult{
Alias: info.Alias,
Pubkey: info.IdentityPubkey,
}, nil
}
func (c *LndClient) IsConnected(destination []byte) (bool, error) {
pubKey := hex.EncodeToString(destination)
r, err := c.client.ListPeers(context.Background(), &lnrpc.ListPeersRequest{LatestError: true})
if err != nil {
log.Printf("LND: client.ListPeers() error: %v", err)
return false, fmt.Errorf("LND: client.ListPeers() error: %w", err)
}
for _, peer := range r.Peers {
if pubKey == peer.PubKey {
log.Printf("destination online: %x", destination)
return true, nil
}
}
log.Printf("LND: destination offline: %x", destination)
return false, nil
}
func (c *LndClient) OpenChannel(req *lightning.OpenChannelRequest) (*wire.OutPoint, error) {
lnReq := &lnrpc.OpenChannelRequest{
NodePubkey: req.Destination,
LocalFundingAmount: int64(req.CapacitySat),
PushSat: 0,
Private: req.IsPrivate,
CommitmentType: lnrpc.CommitmentType_ANCHORS,
ZeroConf: req.IsZeroConf,
}
if req.MinConfs != nil {
minConfs := *req.MinConfs
lnReq.MinConfs = int32(minConfs)
if minConfs == 0 {
lnReq.SpendUnconfirmed = true
}
}
if req.FeeSatPerVByte != nil {
lnReq.SatPerVbyte = uint64(*req.FeeSatPerVByte)
} else if req.TargetConf != nil {
lnReq.TargetConf = int32(*req.TargetConf)
}
channelPoint, err := c.client.OpenChannelSync(context.Background(), lnReq)
if err != nil {
log.Printf("LND: client.OpenChannelSync(%x, %v) error: %v", req.Destination, req.CapacitySat, err)
return nil, fmt.Errorf("LND: OpenChannel() error: %w", err)
}
result, err := basetypes.NewOutPoint(channelPoint.GetFundingTxidBytes(), channelPoint.OutputIndex)
if err != nil {
log.Printf("LND: OpenChannel returned invalid outpoint. error: %v", err)
return nil, err
}
return result, nil
}
func (c *LndClient) GetChannel(peerID []byte, channelPoint wire.OutPoint) (*lightning.GetChannelResult, error) {
r, err := c.client.ListChannels(context.Background(), &lnrpc.ListChannelsRequest{Peer: peerID})
if err != nil {
log.Printf("client.ListChannels(%x) error: %v", peerID, err)
return nil, err
}
channelPointStr := channelPoint.String()
if err != nil {
return nil, err
}
for _, c := range r.Channels {
log.Printf("getChannel(%x): %v", peerID, c.ChanId)
if c.ChannelPoint == channelPointStr && c.Active {
confirmedChanId := c.ChanId
if c.ZeroConf {
confirmedChanId = c.ZeroConfConfirmedScid
if confirmedChanId == hop.Source.ToUint64() {
confirmedChanId = 0
}
}
return &lightning.GetChannelResult{
InitialChannelID: basetypes.ShortChannelID(c.ChanId),
ConfirmedChannelID: basetypes.ShortChannelID(confirmedChanId),
}, nil
}
}
log.Printf("No channel found: getChannel(%x)", peerID)
return nil, fmt.Errorf("no channel found")
}
func (c *LndClient) GetNodeChannelCount(nodeID []byte) (int, error) {
nodeIDStr := hex.EncodeToString(nodeID)
listResponse, err := c.client.ListChannels(context.Background(), &lnrpc.ListChannelsRequest{})
if err != nil {
return 0, err
}
pendingResponse, err := c.client.PendingChannels(context.Background(), &lnrpc.PendingChannelsRequest{})
if err != nil {
return 0, err
}
count := 0
for _, channel := range listResponse.Channels {
if channel.RemotePubkey == nodeIDStr {
count++
}
}
for _, p := range pendingResponse.PendingOpenChannels {
if p.Channel.RemoteNodePub == nodeIDStr {
count++
}
}
return count, nil
}
func (c *LndClient) GetClosedChannels(nodeID string, channelPoints map[string]uint64) (map[string]uint64, error) {
r := make(map[string]uint64)
if len(channelPoints) == 0 {
return r, nil
}
waitingCloseChannels, err := c.getWaitingCloseChannels(nodeID)
if err != nil {
return nil, err
}
wcc := make(map[string]struct{})
for _, c := range waitingCloseChannels {
wcc[c.Channel.ChannelPoint] = struct{}{}
}
for c, h := range channelPoints {
if _, ok := wcc[c]; !ok {
r[c] = h
}
}
return r, nil
}
func (c *LndClient) getWaitingCloseChannels(nodeID string) ([]*lnrpc.PendingChannelsResponse_WaitingCloseChannel, error) {
pendingResponse, err := c.client.PendingChannels(context.Background(), &lnrpc.PendingChannelsRequest{})
if err != nil {
return nil, err
}
var waitingCloseChannels []*lnrpc.PendingChannelsResponse_WaitingCloseChannel
for _, p := range pendingResponse.WaitingCloseChannels {
if p.Channel.RemoteNodePub == nodeID {
waitingCloseChannels = append(waitingCloseChannels, p)
}
}
return waitingCloseChannels, nil
}