mirror of
https://github.com/aljazceru/lspd.git
synced 2025-12-18 14:24:21 +01:00
417 lines
14 KiB
Go
417 lines
14 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"crypto/tls"
|
|
"crypto/x509"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"log"
|
|
"net"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/breez/lspd/btceclegacy"
|
|
lspdrpc "github.com/breez/lspd/rpc"
|
|
"github.com/golang/protobuf/proto"
|
|
|
|
"github.com/btcsuite/btcd/btcec/v2"
|
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
|
"github.com/caddyserver/certmagic"
|
|
grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
|
|
"github.com/lightningnetwork/lnd/lnrpc"
|
|
"github.com/lightningnetwork/lnd/lnrpc/chainrpc"
|
|
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
|
|
"github.com/lightningnetwork/lnd/lnwire"
|
|
"golang.org/x/sync/singleflight"
|
|
"google.golang.org/grpc"
|
|
"google.golang.org/grpc/codes"
|
|
"google.golang.org/grpc/credentials"
|
|
"google.golang.org/grpc/metadata"
|
|
"google.golang.org/grpc/status"
|
|
)
|
|
|
|
const (
|
|
publicChannelAmount = 1_000_183
|
|
targetConf = 6
|
|
minHtlcMsat = 600
|
|
baseFeeMsat = 1000
|
|
feeRate = 0.000001
|
|
timeLockDelta = 144
|
|
channelFeePermyriad = 40
|
|
channelMinimumFeeMsat = 2_000_000
|
|
additionalChannelCapacity = 100_000
|
|
maxInactiveDuration = 45 * 24 * 3600
|
|
)
|
|
|
|
type server struct{}
|
|
|
|
var (
|
|
client lnrpc.LightningClient
|
|
routerClient routerrpc.RouterClient
|
|
chainNotifierClient chainrpc.ChainNotifierClient
|
|
openChannelReqGroup singleflight.Group
|
|
privateKey *btcec.PrivateKey
|
|
publicKey *btcec.PublicKey
|
|
nodeName = os.Getenv("NODE_NAME")
|
|
nodePubkey = os.Getenv("NODE_PUBKEY")
|
|
)
|
|
|
|
func (s *server) ChannelInformation(ctx context.Context, in *lspdrpc.ChannelInformationRequest) (*lspdrpc.ChannelInformationReply, error) {
|
|
return &lspdrpc.ChannelInformationReply{
|
|
Name: nodeName,
|
|
Pubkey: nodePubkey,
|
|
Host: os.Getenv("NODE_HOST"),
|
|
ChannelCapacity: publicChannelAmount,
|
|
TargetConf: targetConf,
|
|
MinHtlcMsat: minHtlcMsat,
|
|
BaseFeeMsat: baseFeeMsat,
|
|
FeeRate: feeRate,
|
|
TimeLockDelta: timeLockDelta,
|
|
ChannelFeePermyriad: channelFeePermyriad,
|
|
ChannelMinimumFeeMsat: channelMinimumFeeMsat,
|
|
LspPubkey: publicKey.SerializeCompressed(),
|
|
MaxInactiveDuration: maxInactiveDuration,
|
|
}, nil
|
|
}
|
|
|
|
func (s *server) RegisterPayment(ctx context.Context, in *lspdrpc.RegisterPaymentRequest) (*lspdrpc.RegisterPaymentReply, error) {
|
|
data, err := btceclegacy.Decrypt(privateKey, in.Blob)
|
|
if err != nil {
|
|
log.Printf("btcec.Decrypt(%x) error: %v", in.Blob, err)
|
|
return nil, fmt.Errorf("btcec.Decrypt(%x) error: %w", in.Blob, err)
|
|
}
|
|
var pi lspdrpc.PaymentInformation
|
|
err = proto.Unmarshal(data, &pi)
|
|
if err != nil {
|
|
log.Printf("proto.Unmarshal(%x) error: %v", data, err)
|
|
return nil, fmt.Errorf("proto.Unmarshal(%x) error: %w", data, err)
|
|
}
|
|
log.Printf("RegisterPayment - Destination: %x, pi.PaymentHash: %x, pi.PaymentSecret: %x, pi.IncomingAmountMsat: %v, pi.OutgoingAmountMsat: %v",
|
|
pi.Destination, pi.PaymentHash, pi.PaymentSecret, pi.IncomingAmountMsat, pi.OutgoingAmountMsat)
|
|
err = checkPayment(pi.IncomingAmountMsat, pi.OutgoingAmountMsat)
|
|
if err != nil {
|
|
log.Printf("checkPayment(%v, %v) error: %v", pi.IncomingAmountMsat, pi.OutgoingAmountMsat, err)
|
|
return nil, fmt.Errorf("checkPayment(%v, %v) error: %v", pi.IncomingAmountMsat, pi.OutgoingAmountMsat, err)
|
|
}
|
|
err = registerPayment(pi.Destination, pi.PaymentHash, pi.PaymentSecret, pi.IncomingAmountMsat, pi.OutgoingAmountMsat)
|
|
if err != nil {
|
|
log.Printf("RegisterPayment() error: %v", err)
|
|
return nil, fmt.Errorf("RegisterPayment() error: %w", err)
|
|
}
|
|
return &lspdrpc.RegisterPaymentReply{}, nil
|
|
}
|
|
|
|
func (s *server) OpenChannel(ctx context.Context, in *lspdrpc.OpenChannelRequest) (*lspdrpc.OpenChannelReply, error) {
|
|
r, err, _ := openChannelReqGroup.Do(in.Pubkey, func() (interface{}, error) {
|
|
clientCtx := metadata.AppendToOutgoingContext(context.Background(), "macaroon", os.Getenv("LND_MACAROON_HEX"))
|
|
nodeChannels, err := getNodeChannels(in.Pubkey)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
pendingChannels, err := getPendingNodeChannels(in.Pubkey)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
channelAmount, err := strconv.ParseInt(os.Getenv("CHANNEL_AMOUNT"), 0, 64)
|
|
if err != nil || channelAmount <= 0 {
|
|
channelAmount = publicChannelAmount
|
|
}
|
|
log.Printf("os.Getenv(\"CHANNEL_AMOUNT\"): %v, channelAmount: %v, publicChannelAmount: %v, err: %v",
|
|
os.Getenv("CHANNEL_AMOUNT"), channelAmount, publicChannelAmount, err)
|
|
isPrivate, err := strconv.ParseBool(os.Getenv("CHANNEL_PRIVATE"))
|
|
if err != nil {
|
|
isPrivate = false
|
|
}
|
|
log.Printf("os.Getenv(\"CHANNEL_PRIVATE\"): %v, isPrivate: %v, err: %v",
|
|
os.Getenv("CHANNEL_PRIVATE"), isPrivate, err)
|
|
var txidStr string
|
|
var outputIndex uint32
|
|
if len(nodeChannels) == 0 && len(pendingChannels) == 0 {
|
|
response, err := client.OpenChannelSync(clientCtx, &lnrpc.OpenChannelRequest{
|
|
LocalFundingAmount: channelAmount,
|
|
NodePubkeyString: in.Pubkey,
|
|
PushSat: 0,
|
|
TargetConf: targetConf,
|
|
MinHtlcMsat: minHtlcMsat,
|
|
Private: isPrivate,
|
|
})
|
|
log.Printf("Response from OpenChannel: %#v (TX: %v)", response, hex.EncodeToString(response.GetFundingTxidBytes()))
|
|
|
|
if err != nil {
|
|
log.Printf("Error in OpenChannel: %v", err)
|
|
return nil, err
|
|
}
|
|
|
|
txid, _ := chainhash.NewHash(response.GetFundingTxidBytes())
|
|
outputIndex = response.GetOutputIndex()
|
|
// don't fail the request in case we can't format the channel id from
|
|
// some reason...
|
|
if txid != nil {
|
|
txidStr = txid.String()
|
|
}
|
|
}
|
|
return &lspdrpc.OpenChannelReply{TxHash: txidStr, OutputIndex: outputIndex}, nil
|
|
})
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return r.(*lspdrpc.OpenChannelReply), err
|
|
}
|
|
|
|
func getSignedEncryptedData(in *lspdrpc.Encrypted) (string, []byte, error) {
|
|
signedBlob, err := btceclegacy.Decrypt(privateKey, in.Data)
|
|
if err != nil {
|
|
log.Printf("btcec.Decrypt(%x) error: %v", in.Data, err)
|
|
return "", nil, fmt.Errorf("btcec.Decrypt(%x) error: %w", in.Data, err)
|
|
}
|
|
var signed lspdrpc.Signed
|
|
err = proto.Unmarshal(signedBlob, &signed)
|
|
if err != nil {
|
|
log.Printf("proto.Unmarshal(%x) error: %v", signedBlob, err)
|
|
return "", nil, fmt.Errorf("proto.Unmarshal(%x) error: %w", signedBlob, err)
|
|
}
|
|
pubkey, err := btcec.ParsePubKey(signed.Pubkey)
|
|
if err != nil {
|
|
log.Printf("unable to parse pubkey: %v", err)
|
|
return "", nil, fmt.Errorf("unable to parse pubkey: %w", err)
|
|
}
|
|
wireSig, err := lnwire.NewSigFromRawSignature(signed.Signature)
|
|
if err != nil {
|
|
return "", nil, fmt.Errorf("failed to decode signature: %v", err)
|
|
}
|
|
sig, err := wireSig.ToSignature()
|
|
if err != nil {
|
|
return "", nil, fmt.Errorf("failed to convert from wire format: %v",
|
|
err)
|
|
}
|
|
// The signature is over the sha256 hash of the message.
|
|
digest := chainhash.HashB(signed.Data)
|
|
if !sig.Verify(digest, pubkey) {
|
|
return "", nil, fmt.Errorf("invalid signature")
|
|
}
|
|
return hex.EncodeToString(signed.Pubkey), signed.Data, nil
|
|
}
|
|
|
|
func (s *server) CheckChannels(ctx context.Context, in *lspdrpc.Encrypted) (*lspdrpc.Encrypted, error) {
|
|
nodeID, data, err := getSignedEncryptedData(in)
|
|
if err != nil {
|
|
log.Printf("getSignedEncryptedData error: %v", err)
|
|
return nil, fmt.Errorf("getSignedEncryptedData error: %v", err)
|
|
}
|
|
var checkChannelsRequest lspdrpc.CheckChannelsRequest
|
|
err = proto.Unmarshal(data, &checkChannelsRequest)
|
|
if err != nil {
|
|
log.Printf("proto.Unmarshal(%x) error: %v", data, err)
|
|
return nil, fmt.Errorf("proto.Unmarshal(%x) error: %w", data, err)
|
|
}
|
|
notFakeChannels, err := getNotFakeChannels(nodeID, checkChannelsRequest.FakeChannels)
|
|
if err != nil {
|
|
log.Printf("getNotFakeChannels(%v) error: %v", checkChannelsRequest.FakeChannels, err)
|
|
return nil, fmt.Errorf("getNotFakeChannels(%v) error: %w", checkChannelsRequest.FakeChannels, err)
|
|
}
|
|
closedChannels, err := getClosedChannels(nodeID, checkChannelsRequest.WaitingCloseChannels)
|
|
if err != nil {
|
|
log.Printf("getNotFakeChannels(%v) error: %v", checkChannelsRequest.FakeChannels, err)
|
|
return nil, fmt.Errorf("getNotFakeChannels(%v) error: %w", checkChannelsRequest.FakeChannels, err)
|
|
}
|
|
checkChannelsReply := lspdrpc.CheckChannelsReply{
|
|
NotFakeChannels: notFakeChannels,
|
|
ClosedChannels: closedChannels,
|
|
}
|
|
dataReply, err := proto.Marshal(&checkChannelsReply)
|
|
if err != nil {
|
|
log.Printf("proto.Marshall() error: %v", err)
|
|
return nil, fmt.Errorf("proto.Marshal() error: %w", err)
|
|
}
|
|
pubkey, err := btcec.ParsePubKey(checkChannelsRequest.EncryptPubkey)
|
|
if err != nil {
|
|
log.Printf("unable to parse pubkey: %v", err)
|
|
return nil, fmt.Errorf("unable to parse pubkey: %w", err)
|
|
}
|
|
encrypted, err := btceclegacy.Encrypt(pubkey, dataReply)
|
|
if err != nil {
|
|
log.Printf("btcec.Encrypt() error: %v", err)
|
|
return nil, fmt.Errorf("btcec.Encrypt() error: %w", err)
|
|
}
|
|
return &lspdrpc.Encrypted{Data: encrypted}, nil
|
|
}
|
|
|
|
func getNotFakeChannels(nodeID string, channelPoints map[string]uint64) (map[string]uint64, error) {
|
|
r := make(map[string]uint64)
|
|
if len(channelPoints) == 0 {
|
|
return r, nil
|
|
}
|
|
channels, err := confirmedChannels(nodeID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for channelPoint, chanID := range channels {
|
|
if _, ok := channelPoints[channelPoint]; ok {
|
|
r[channelPoint] = chanID
|
|
}
|
|
}
|
|
return r, nil
|
|
}
|
|
|
|
func 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 := 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 getWaitingCloseChannels(nodeID string) ([]*lnrpc.PendingChannelsResponse_WaitingCloseChannel, error) {
|
|
clientCtx := metadata.AppendToOutgoingContext(context.Background(), "macaroon", os.Getenv("LND_MACAROON_HEX"))
|
|
pendingResponse, err := client.PendingChannels(clientCtx, &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
|
|
}
|
|
|
|
func getNodeChannels(nodeID string) ([]*lnrpc.Channel, error) {
|
|
clientCtx := metadata.AppendToOutgoingContext(context.Background(), "macaroon", os.Getenv("LND_MACAROON_HEX"))
|
|
listResponse, err := client.ListChannels(clientCtx, &lnrpc.ListChannelsRequest{})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var nodeChannels []*lnrpc.Channel
|
|
for _, channel := range listResponse.Channels {
|
|
if channel.RemotePubkey == nodeID {
|
|
nodeChannels = append(nodeChannels, channel)
|
|
}
|
|
}
|
|
return nodeChannels, nil
|
|
}
|
|
|
|
func getPendingNodeChannels(nodeID string) ([]*lnrpc.PendingChannelsResponse_PendingOpenChannel, error) {
|
|
clientCtx := metadata.AppendToOutgoingContext(context.Background(), "macaroon", os.Getenv("LND_MACAROON_HEX"))
|
|
pendingResponse, err := client.PendingChannels(clientCtx, &lnrpc.PendingChannelsRequest{})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var pendingChannels []*lnrpc.PendingChannelsResponse_PendingOpenChannel
|
|
for _, p := range pendingResponse.PendingOpenChannels {
|
|
if p.Channel.RemoteNodePub == nodeID {
|
|
pendingChannels = append(pendingChannels, p)
|
|
}
|
|
}
|
|
return pendingChannels, nil
|
|
}
|
|
|
|
func main() {
|
|
if len(os.Args) > 1 && os.Args[1] == "genkey" {
|
|
p, err := btcec.NewPrivateKey()
|
|
if err != nil {
|
|
log.Fatalf("btcec.NewPrivateKey() error: %v", err)
|
|
}
|
|
fmt.Printf("LSPD_PRIVATE_KEY=\"%x\"\n", p.Serialize())
|
|
return
|
|
}
|
|
|
|
err := pgConnect()
|
|
if err != nil {
|
|
log.Fatalf("pgConnect() error: %v", err)
|
|
}
|
|
|
|
privateKeyBytes, err := hex.DecodeString(os.Getenv("LSPD_PRIVATE_KEY"))
|
|
if err != nil {
|
|
log.Fatalf("hex.DecodeString(os.Getenv(\"LSPD_PRIVATE_KEY\")=%v) error: %v", os.Getenv("LSPD_PRIVATE_KEY"), err)
|
|
}
|
|
privateKey, publicKey = btcec.PrivKeyFromBytes(privateKeyBytes)
|
|
|
|
certmagicDomain := os.Getenv("CERTMAGIC_DOMAIN")
|
|
address := os.Getenv("LISTEN_ADDRESS")
|
|
var lis net.Listener
|
|
if certmagicDomain == "" {
|
|
var err error
|
|
lis, err = net.Listen("tcp", address)
|
|
if err != nil {
|
|
log.Fatalf("failed to listen: %v", err)
|
|
}
|
|
} else {
|
|
tlsConfig, err := certmagic.TLS([]string{certmagicDomain})
|
|
if err != nil {
|
|
log.Fatalf("failed to run certmagic: %v", err)
|
|
}
|
|
lis, err = tls.Listen("tcp", address, tlsConfig)
|
|
if err != nil {
|
|
log.Fatalf("failed to listen: %v", err)
|
|
}
|
|
}
|
|
|
|
// Creds file to connect to LND gRPC
|
|
cp := x509.NewCertPool()
|
|
if !cp.AppendCertsFromPEM([]byte(strings.Replace(os.Getenv("LND_CERT"), "\\n", "\n", -1))) {
|
|
log.Fatalf("credentials: failed to append certificates")
|
|
}
|
|
creds := credentials.NewClientTLSFromCert(cp, "")
|
|
|
|
// Address of an LND instance
|
|
conn, err := grpc.Dial(os.Getenv("LND_ADDRESS"), grpc.WithTransportCredentials(creds))
|
|
if err != nil {
|
|
log.Fatalf("Failed to connect to LND gRPC: %v", err)
|
|
}
|
|
defer conn.Close()
|
|
client = lnrpc.NewLightningClient(conn)
|
|
routerClient = routerrpc.NewRouterClient(conn)
|
|
chainNotifierClient = chainrpc.NewChainNotifierClient(conn)
|
|
|
|
clientCtx := metadata.AppendToOutgoingContext(context.Background(), "macaroon", os.Getenv("LND_MACAROON_HEX"))
|
|
info, err := client.GetInfo(clientCtx, &lnrpc.GetInfoRequest{})
|
|
if err != nil {
|
|
log.Fatalf("client.GetInfo() error: %v", err)
|
|
}
|
|
if nodeName == "" {
|
|
nodeName = info.Alias
|
|
}
|
|
if nodePubkey == "" {
|
|
nodePubkey = info.IdentityPubkey
|
|
}
|
|
|
|
go intercept()
|
|
|
|
go forwardingHistorySynchronize()
|
|
go channelsSynchronize(chainNotifierClient)
|
|
|
|
s := grpc.NewServer(
|
|
grpc_middleware.WithUnaryServerChain(func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
|
|
if md, ok := metadata.FromIncomingContext(ctx); ok {
|
|
for _, auth := range md.Get("authorization") {
|
|
if auth == "Bearer "+os.Getenv("TOKEN") {
|
|
return handler(ctx, req)
|
|
}
|
|
}
|
|
}
|
|
return nil, status.Errorf(codes.PermissionDenied, "Not authorized")
|
|
}),
|
|
)
|
|
lspdrpc.RegisterChannelOpenerServer(s, &server{})
|
|
if err := s.Serve(lis); err != nil {
|
|
log.Fatalf("failed to serve: %v", err)
|
|
}
|
|
}
|