diff --git a/channel_opener_server.go b/channel_opener_server.go index fdb47a7..8048841 100644 --- a/channel_opener_server.go +++ b/channel_opener_server.go @@ -15,6 +15,7 @@ import ( "github.com/breez/lspd/interceptor" "github.com/breez/lspd/lightning" lspdrpc "github.com/breez/lspd/rpc" + "github.com/breez/lspd/shared" ecies "github.com/ecies/go/v2" "github.com/golang/protobuf/proto" "google.golang.org/grpc/codes" @@ -54,26 +55,26 @@ func (s *channelOpenerServer) ChannelInformation(ctx context.Context, in *lspdrp } return &lspdrpc.ChannelInformationReply{ - Name: node.nodeConfig.Name, - Pubkey: node.nodeConfig.NodePubkey, - Host: node.nodeConfig.Host, - ChannelCapacity: int64(node.nodeConfig.PublicChannelAmount), - TargetConf: int32(node.nodeConfig.TargetConf), - MinHtlcMsat: int64(node.nodeConfig.MinHtlcMsat), - BaseFeeMsat: int64(node.nodeConfig.BaseFeeMsat), - FeeRate: node.nodeConfig.FeeRate, - TimeLockDelta: node.nodeConfig.TimeLockDelta, - ChannelFeePermyriad: int64(node.nodeConfig.ChannelFeePermyriad), - ChannelMinimumFeeMsat: int64(node.nodeConfig.ChannelMinimumFeeMsat), - LspPubkey: node.publicKey.SerializeCompressed(), // TODO: Is the publicKey different from the ecies public key? - MaxInactiveDuration: int64(node.nodeConfig.MaxInactiveDuration), + Name: node.NodeConfig.Name, + Pubkey: node.NodeConfig.NodePubkey, + Host: node.NodeConfig.Host, + ChannelCapacity: int64(node.NodeConfig.PublicChannelAmount), + TargetConf: int32(node.NodeConfig.TargetConf), + MinHtlcMsat: int64(node.NodeConfig.MinHtlcMsat), + BaseFeeMsat: int64(node.NodeConfig.BaseFeeMsat), + FeeRate: node.NodeConfig.FeeRate, + TimeLockDelta: node.NodeConfig.TimeLockDelta, + ChannelFeePermyriad: int64(node.NodeConfig.ChannelFeePermyriad), + ChannelMinimumFeeMsat: int64(node.NodeConfig.ChannelMinimumFeeMsat), + LspPubkey: node.PublicKey.SerializeCompressed(), // TODO: Is the publicKey different from the ecies public key? + MaxInactiveDuration: int64(node.NodeConfig.MaxInactiveDuration), OpeningFeeParamsMenu: params, }, nil } func (s *channelOpenerServer) createOpeningParamsMenu( ctx context.Context, - node *node, + node *shared.Node, token string, ) ([]*lspdrpc.OpeningFeeParams, error) { var menu []*lspdrpc.OpeningFeeParams @@ -136,13 +137,13 @@ func paramsHash(params *lspdrpc.OpeningFeeParams) ([]byte, error) { return hash[:], nil } -func createPromise(node *node, params *lspdrpc.OpeningFeeParams) (*string, error) { +func createPromise(node *shared.Node, params *lspdrpc.OpeningFeeParams) (*string, error) { hash, err := paramsHash(params) if err != nil { return nil, err } // Sign the hash with the private key of the LSP id. - sig, err := ecdsa.SignCompact(node.privateKey, hash[:], true) + sig, err := ecdsa.SignCompact(node.PrivateKey, hash[:], true) if err != nil { log.Printf("createPromise: SignCompact error: %v", err) return nil, err @@ -151,7 +152,7 @@ func createPromise(node *node, params *lspdrpc.OpeningFeeParams) (*string, error return &promise, nil } -func verifyPromise(node *node, params *lspdrpc.OpeningFeeParams) error { +func verifyPromise(node *shared.Node, params *lspdrpc.OpeningFeeParams) error { hash, err := paramsHash(params) if err != nil { return err @@ -166,14 +167,14 @@ func verifyPromise(node *node, params *lspdrpc.OpeningFeeParams) error { log.Printf("verifyPromise: RecoverCompact(%x) error: %v", sig, err) return err } - if !node.publicKey.IsEqual(pub) { + if !node.PublicKey.IsEqual(pub) { log.Print("verifyPromise: not signed by us", err) return fmt.Errorf("invalid promise") } return nil } -func validateOpeningFeeParams(node *node, params *lspdrpc.OpeningFeeParams) bool { +func validateOpeningFeeParams(node *shared.Node, params *lspdrpc.OpeningFeeParams) bool { if params == nil { return false } @@ -206,10 +207,10 @@ func (s *channelOpenerServer) RegisterPayment( return nil, err } - data, err := ecies.Decrypt(node.eciesPrivateKey, in.Blob) + data, err := ecies.Decrypt(node.EciesPrivateKey, in.Blob) if err != nil { log.Printf("ecies.Decrypt(%x) error: %v", in.Blob, err) - data, err = btceclegacy.Decrypt(node.privateKey, in.Blob) + data, err = btceclegacy.Decrypt(node.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) @@ -247,10 +248,10 @@ func (s *channelOpenerServer) RegisterPayment( } else { log.Printf("DEPRECATED: RegisterPayment with deprecated fee mechanism.") pi.OpeningFeeParams = &lspdrpc.OpeningFeeParams{ - MinMsat: uint64(node.nodeConfig.ChannelMinimumFeeMsat), - Proportional: uint32(node.nodeConfig.ChannelFeePermyriad * 100), + MinMsat: uint64(node.NodeConfig.ChannelMinimumFeeMsat), + Proportional: uint32(node.NodeConfig.ChannelFeePermyriad * 100), ValidUntil: time.Now().UTC().Add(time.Duration(time.Hour * 24)).Format(basetypes.TIME_FORMAT), - MaxIdleTime: uint32(node.nodeConfig.MaxInactiveDuration / 600), + MaxIdleTime: uint32(node.NodeConfig.MaxInactiveDuration / 600), MaxClientToSelfDelay: uint32(10000), } } @@ -282,25 +283,25 @@ func (s *channelOpenerServer) OpenChannel(ctx context.Context, in *lspdrpc.OpenC return nil, err } - r, err, _ := node.openChannelReqGroup.Do(in.Pubkey, func() (interface{}, error) { + r, err, _ := node.OpenChannelReqGroup.Do(in.Pubkey, func() (interface{}, error) { pubkey, err := hex.DecodeString(in.Pubkey) if err != nil { return nil, err } - channelCount, err := node.client.GetNodeChannelCount(pubkey) + channelCount, err := node.Client.GetNodeChannelCount(pubkey) if err != nil { return nil, err } var outPoint *wire.OutPoint if channelCount == 0 { - outPoint, err = node.client.OpenChannel(&lightning.OpenChannelRequest{ - CapacitySat: node.nodeConfig.ChannelAmount, + outPoint, err = node.Client.OpenChannel(&lightning.OpenChannelRequest{ + CapacitySat: node.NodeConfig.ChannelAmount, Destination: pubkey, - TargetConf: &node.nodeConfig.TargetConf, - MinHtlcMsat: node.nodeConfig.MinHtlcMsat, - IsPrivate: node.nodeConfig.ChannelPrivate, + TargetConf: &node.NodeConfig.TargetConf, + MinHtlcMsat: node.NodeConfig.MinHtlcMsat, + IsPrivate: node.NodeConfig.ChannelPrivate, }) if err != nil { @@ -320,13 +321,13 @@ func (s *channelOpenerServer) OpenChannel(ctx context.Context, in *lspdrpc.OpenC return r.(*lspdrpc.OpenChannelReply), err } -func (n *node) getSignedEncryptedData(in *lspdrpc.Encrypted) (string, []byte, bool, error) { +func getSignedEncryptedData(n *shared.Node, in *lspdrpc.Encrypted) (string, []byte, bool, error) { usedEcies := true - signedBlob, err := ecies.Decrypt(n.eciesPrivateKey, in.Data) + signedBlob, err := ecies.Decrypt(n.EciesPrivateKey, in.Data) if err != nil { log.Printf("ecies.Decrypt(%x) error: %v", in.Data, err) usedEcies = false - signedBlob, err = btceclegacy.Decrypt(n.privateKey, in.Data) + signedBlob, err = btceclegacy.Decrypt(n.PrivateKey, in.Data) if err != nil { log.Printf("btcec.Decrypt(%x) error: %v", in.Data, err) return "", nil, usedEcies, fmt.Errorf("btcec.Decrypt(%x) error: %w", in.Data, err) @@ -366,7 +367,7 @@ func (s *channelOpenerServer) CheckChannels(ctx context.Context, in *lspdrpc.Enc return nil, err } - nodeID, data, usedEcies, err := node.getSignedEncryptedData(in) + nodeID, data, usedEcies, err := getSignedEncryptedData(node, in) if err != nil { log.Printf("getSignedEncryptedData error: %v", err) return nil, fmt.Errorf("getSignedEncryptedData error: %v", err) @@ -377,7 +378,7 @@ func (s *channelOpenerServer) CheckChannels(ctx context.Context, in *lspdrpc.Enc log.Printf("proto.Unmarshal(%x) error: %v", data, err) return nil, fmt.Errorf("proto.Unmarshal(%x) error: %w", data, err) } - closedChannels, err := node.client.GetClosedChannels(nodeID, checkChannelsRequest.WaitingCloseChannels) + closedChannels, err := node.Client.GetClosedChannels(nodeID, checkChannelsRequest.WaitingCloseChannels) if err != nil { log.Printf("GetClosedChannels(%v) error: %v", checkChannelsRequest.FakeChannels, err) return nil, fmt.Errorf("GetClosedChannels(%v) error: %w", checkChannelsRequest.FakeChannels, err) @@ -399,7 +400,7 @@ func (s *channelOpenerServer) CheckChannels(ctx context.Context, in *lspdrpc.Enc var encrypted []byte if usedEcies { - encrypted, err = ecies.Encrypt(node.eciesPublicKey, dataReply) + encrypted, err = ecies.Encrypt(node.EciesPublicKey, dataReply) if err != nil { log.Printf("ecies.Encrypt() error: %v", err) return nil, fmt.Errorf("ecies.Encrypt() error: %w", err) @@ -415,7 +416,7 @@ func (s *channelOpenerServer) CheckChannels(ctx context.Context, in *lspdrpc.Enc return &lspdrpc.Encrypted{Data: encrypted}, nil } -func (s *channelOpenerServer) getNode(ctx context.Context) (*node, string, error) { +func (s *channelOpenerServer) getNode(ctx context.Context) (*shared.Node, string, error) { nd := ctx.Value(contextKey("node")) if nd == nil { return nil, "", status.Errorf(codes.PermissionDenied, "Not authorized") diff --git a/grpc_server.go b/grpc_server.go index d3055a5..7d2d0f6 100644 --- a/grpc_server.go +++ b/grpc_server.go @@ -3,23 +3,16 @@ package main import ( "context" "crypto/tls" - "encoding/hex" "fmt" "log" "net" "strings" - "github.com/breez/lspd/cln" - "github.com/breez/lspd/config" - "github.com/breez/lspd/lightning" - "github.com/breez/lspd/lnd" "github.com/breez/lspd/notifications" lspdrpc "github.com/breez/lspd/rpc" - "github.com/btcsuite/btcd/btcec/v2" + "github.com/breez/lspd/shared" "github.com/caddyserver/certmagic" - ecies "github.com/ecies/go/v2" grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware" - "golang.org/x/sync/singleflight" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/metadata" @@ -27,119 +20,38 @@ import ( ) type grpcServer struct { + nodesService shared.NodesService address string certmagicDomain string lis net.Listener s *grpc.Server - nodes map[string]*node c lspdrpc.ChannelOpenerServer n notifications.NotificationsServer } type nodeContext struct { token string - node *node -} - -type node struct { - client lightning.Client - nodeConfig *config.NodeConfig - privateKey *btcec.PrivateKey - publicKey *btcec.PublicKey - eciesPrivateKey *ecies.PrivateKey - eciesPublicKey *ecies.PublicKey - openChannelReqGroup singleflight.Group + node *shared.Node } func NewGrpcServer( - configs []*config.NodeConfig, + nodesService shared.NodesService, address string, certmagicDomain string, c lspdrpc.ChannelOpenerServer, n notifications.NotificationsServer, ) (*grpcServer, error) { - if len(configs) == 0 { - return nil, fmt.Errorf("no nodes supplied") - } - - nodes := make(map[string]*node) - for _, config := range configs { - pk, err := hex.DecodeString(config.LspdPrivateKey) - if err != nil { - return nil, fmt.Errorf("hex.DecodeString(config.lspdPrivateKey=%v) error: %v", config.LspdPrivateKey, err) - } - - eciesPrivateKey := ecies.NewPrivateKeyFromBytes(pk) - eciesPublicKey := eciesPrivateKey.PublicKey - privateKey, publicKey := btcec.PrivKeyFromBytes(pk) - - node := &node{ - nodeConfig: config, - privateKey: privateKey, - publicKey: publicKey, - eciesPrivateKey: eciesPrivateKey, - eciesPublicKey: eciesPublicKey, - } - - if config.Lnd == nil && config.Cln == nil { - return nil, fmt.Errorf("node has to be either cln or lnd") - } - - if config.Lnd != nil && config.Cln != nil { - return nil, fmt.Errorf("node cannot be both cln and lnd") - } - - if config.Lnd != nil { - node.client, err = lnd.NewLndClient(config.Lnd) - if err != nil { - return nil, err - } - } - - if config.Cln != nil { - node.client, err = cln.NewClnClient(config.Cln.SocketPath) - if err != nil { - return nil, err - } - } - - for _, token := range config.Tokens { - _, exists := nodes[token] - if exists { - return nil, fmt.Errorf("cannot have multiple nodes with the same token") - } - - nodes[token] = node - } - } return &grpcServer{ + nodesService: nodesService, address: address, certmagicDomain: certmagicDomain, - nodes: nodes, c: c, n: n, }, nil } func (s *grpcServer) Start() error { - // Make sure all nodes are available and set name and pubkey if not set - // in config. - for _, n := range s.nodes { - info, err := n.client.GetInfo() - if err != nil { - return fmt.Errorf("failed to get info from host %s", n.nodeConfig.Host) - } - - if n.nodeConfig.Name == "" { - n.nodeConfig.Name = info.Alias - } - - if n.nodeConfig.NodePubkey == "" { - n.nodeConfig.NodePubkey = info.Pubkey - } - } - var lis net.Listener if s.certmagicDomain == "" { var err error @@ -167,8 +79,8 @@ func (s *grpcServer) Start() error { } token := strings.Replace(auth, "Bearer ", "", 1) - node, ok := s.nodes[token] - if !ok { + node, err := s.nodesService.GetNode(token) + if err != nil { continue } diff --git a/main.go b/main.go index 037bc92..e0f12ed 100644 --- a/main.go +++ b/main.go @@ -20,6 +20,7 @@ import ( "github.com/breez/lspd/mempool" "github.com/breez/lspd/notifications" "github.com/breez/lspd/postgresql" + "github.com/breez/lspd/shared" "github.com/btcsuite/btcd/btcec/v2" ) @@ -44,6 +45,11 @@ func main() { log.Fatalf("need at least one node configured in NODES.") } + nodesService, err := shared.NewNodesService(nodes) + if err != nil { + log.Fatalf("failed to create nodes service: %v", err) + } + mempoolUrl := os.Getenv("MEMPOOL_API_BASE_URL") if mempoolUrl == "" { log.Fatalf("No mempool url configured.") @@ -136,7 +142,7 @@ func main() { certMagicDomain := os.Getenv("CERTMAGIC_DOMAIN") cs := NewChannelOpenerServer(interceptStore) ns := notifications.NewNotificationsServer(notificationsStore) - s, err := NewGrpcServer(nodes, address, certMagicDomain, cs, ns) + s, err := NewGrpcServer(nodesService, address, certMagicDomain, cs, ns) if err != nil { log.Fatalf("failed to initialize grpc server: %v", err) } diff --git a/shared/nodes_service.go b/shared/nodes_service.go new file mode 100644 index 0000000..89edaa1 --- /dev/null +++ b/shared/nodes_service.go @@ -0,0 +1,118 @@ +package shared + +import ( + "encoding/hex" + "errors" + "fmt" + + "github.com/breez/lspd/cln" + "github.com/breez/lspd/config" + "github.com/breez/lspd/lightning" + "github.com/breez/lspd/lnd" + "github.com/btcsuite/btcd/btcec/v2" + ecies "github.com/ecies/go/v2" + "golang.org/x/sync/singleflight" +) + +type Node struct { + Client lightning.Client + NodeConfig *config.NodeConfig + PrivateKey *btcec.PrivateKey + PublicKey *btcec.PublicKey + EciesPrivateKey *ecies.PrivateKey + EciesPublicKey *ecies.PublicKey + OpenChannelReqGroup singleflight.Group +} + +type NodesService interface { + GetNode(token string) (*Node, error) +} +type nodesService struct { + nodes map[string]*Node +} + +func NewNodesService(configs []*config.NodeConfig) (NodesService, error) { + if len(configs) == 0 { + return nil, fmt.Errorf("no nodes supplied") + } + + nodes := make(map[string]*Node) + for _, config := range configs { + pk, err := hex.DecodeString(config.LspdPrivateKey) + if err != nil { + return nil, fmt.Errorf("hex.DecodeString(config.lspdPrivateKey=%v) error: %v", config.LspdPrivateKey, err) + } + + eciesPrivateKey := ecies.NewPrivateKeyFromBytes(pk) + eciesPublicKey := eciesPrivateKey.PublicKey + privateKey, publicKey := btcec.PrivKeyFromBytes(pk) + node := &Node{ + NodeConfig: config, + PrivateKey: privateKey, + PublicKey: publicKey, + EciesPrivateKey: eciesPrivateKey, + EciesPublicKey: eciesPublicKey, + } + + if config.Lnd == nil && config.Cln == nil { + return nil, fmt.Errorf("node has to be either cln or lnd") + } + + if config.Lnd != nil && config.Cln != nil { + return nil, fmt.Errorf("node cannot be both cln and lnd") + } + + if config.Lnd != nil { + node.Client, err = lnd.NewLndClient(config.Lnd) + if err != nil { + return nil, err + } + } + + if config.Cln != nil { + node.Client, err = cln.NewClnClient(config.Cln.SocketPath) + if err != nil { + return nil, err + } + } + + // Make sure the nodes is available and set name and pubkey if not set + // in config. + info, err := node.Client.GetInfo() + if err != nil { + return nil, fmt.Errorf("failed to get info from host %s", node.NodeConfig.Host) + } + + if node.NodeConfig.Name == "" { + node.NodeConfig.Name = info.Alias + } + + if node.NodeConfig.NodePubkey == "" { + node.NodeConfig.NodePubkey = info.Pubkey + } + + for _, token := range config.Tokens { + _, exists := nodes[token] + if exists { + return nil, fmt.Errorf("cannot have multiple nodes with the same token") + } + + nodes[token] = node + } + } + + return &nodesService{ + nodes: nodes, + }, nil +} + +var ErrNodeNotFound = errors.New("node not found") + +func (s *nodesService) GetNode(token string) (*Node, error) { + node, ok := s.nodes[token] + if !ok { + return nil, ErrNodeNotFound + } + + return node, nil +}