From 192135b224a9a3b29de7ffc1d042ebe52f3ac597 Mon Sep 17 00:00:00 2001 From: Yaacov Akiba Slama Date: Wed, 17 Jul 2019 17:32:47 +0300 Subject: [PATCH 001/214] First version --- go.mod | 12 ++ rpc/genproto.sh | 2 + rpc/lspd.pb.go | 364 ++++++++++++++++++++++++++++++++++++++++++++++++ rpc/lspd.proto | 38 +++++ sample.env | 8 ++ server.go | 143 +++++++++++++++++++ 6 files changed, 567 insertions(+) create mode 100644 go.mod create mode 100755 rpc/genproto.sh create mode 100644 rpc/lspd.pb.go create mode 100644 rpc/lspd.proto create mode 100644 sample.env create mode 100644 server.go diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..e0c192e --- /dev/null +++ b/go.mod @@ -0,0 +1,12 @@ +module lspd + +go 1.12 + +require ( + github.com/btcsuite/btcd v0.0.0-20190629003639-c26ffa870fd8 + github.com/golang/protobuf v1.3.2 + github.com/lightningnetwork/lnd v0.7.0-beta + golang.org/x/net v0.0.0-20190628185345-da137c7871d7 + golang.org/x/sync v0.0.0-20190423024810-112230192c58 + google.golang.org/grpc v1.22.0 +) diff --git a/rpc/genproto.sh b/rpc/genproto.sh new file mode 100755 index 0000000..6a2b80a --- /dev/null +++ b/rpc/genproto.sh @@ -0,0 +1,2 @@ +#!/bin/bash +protoc -I . lspd.proto --go_out=plugins=grpc:. diff --git a/rpc/lspd.pb.go b/rpc/lspd.pb.go new file mode 100644 index 0000000..bcb2f35 --- /dev/null +++ b/rpc/lspd.pb.go @@ -0,0 +1,364 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// source: lspd.proto + +package lspd + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" + +import ( + context "golang.org/x/net/context" + grpc "google.golang.org/grpc" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package + +type CanOpenChannelRequest struct { + // / The identity pubkey of the Lightning node + Pubkey string `protobuf:"bytes,1,opt,name=pubkey,proto3" json:"pubkey,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *CanOpenChannelRequest) Reset() { *m = CanOpenChannelRequest{} } +func (m *CanOpenChannelRequest) String() string { return proto.CompactTextString(m) } +func (*CanOpenChannelRequest) ProtoMessage() {} +func (*CanOpenChannelRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_lspd_ebfec18e50d1a22e, []int{0} +} +func (m *CanOpenChannelRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_CanOpenChannelRequest.Unmarshal(m, b) +} +func (m *CanOpenChannelRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_CanOpenChannelRequest.Marshal(b, m, deterministic) +} +func (dst *CanOpenChannelRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_CanOpenChannelRequest.Merge(dst, src) +} +func (m *CanOpenChannelRequest) XXX_Size() int { + return xxx_messageInfo_CanOpenChannelRequest.Size(m) +} +func (m *CanOpenChannelRequest) XXX_DiscardUnknown() { + xxx_messageInfo_CanOpenChannelRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_CanOpenChannelRequest proto.InternalMessageInfo + +func (m *CanOpenChannelRequest) GetPubkey() string { + if m != nil { + return m.Pubkey + } + return "" +} + +type LightningAddress struct { + // / The identity pubkey of the Lightning node + Pubkey string `protobuf:"bytes,1,opt,name=pubkey,proto3" json:"pubkey,omitempty"` + // / The network location of the lightning node, e.g. `12.34.56.78:9012` or `localhost:10011` + Host string `protobuf:"bytes,2,opt,name=host,proto3" json:"host,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *LightningAddress) Reset() { *m = LightningAddress{} } +func (m *LightningAddress) String() string { return proto.CompactTextString(m) } +func (*LightningAddress) ProtoMessage() {} +func (*LightningAddress) Descriptor() ([]byte, []int) { + return fileDescriptor_lspd_ebfec18e50d1a22e, []int{1} +} +func (m *LightningAddress) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_LightningAddress.Unmarshal(m, b) +} +func (m *LightningAddress) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_LightningAddress.Marshal(b, m, deterministic) +} +func (dst *LightningAddress) XXX_Merge(src proto.Message) { + xxx_messageInfo_LightningAddress.Merge(dst, src) +} +func (m *LightningAddress) XXX_Size() int { + return xxx_messageInfo_LightningAddress.Size(m) +} +func (m *LightningAddress) XXX_DiscardUnknown() { + xxx_messageInfo_LightningAddress.DiscardUnknown(m) +} + +var xxx_messageInfo_LightningAddress proto.InternalMessageInfo + +func (m *LightningAddress) GetPubkey() string { + if m != nil { + return m.Pubkey + } + return "" +} + +func (m *LightningAddress) GetHost() string { + if m != nil { + return m.Host + } + return "" +} + +type CanOpenChannelReply struct { + // / Lightning address of the peer, in the format `@host` + Addr *LightningAddress `protobuf:"bytes,1,opt,name=addr,proto3" json:"addr,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *CanOpenChannelReply) Reset() { *m = CanOpenChannelReply{} } +func (m *CanOpenChannelReply) String() string { return proto.CompactTextString(m) } +func (*CanOpenChannelReply) ProtoMessage() {} +func (*CanOpenChannelReply) Descriptor() ([]byte, []int) { + return fileDescriptor_lspd_ebfec18e50d1a22e, []int{2} +} +func (m *CanOpenChannelReply) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_CanOpenChannelReply.Unmarshal(m, b) +} +func (m *CanOpenChannelReply) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_CanOpenChannelReply.Marshal(b, m, deterministic) +} +func (dst *CanOpenChannelReply) XXX_Merge(src proto.Message) { + xxx_messageInfo_CanOpenChannelReply.Merge(dst, src) +} +func (m *CanOpenChannelReply) XXX_Size() int { + return xxx_messageInfo_CanOpenChannelReply.Size(m) +} +func (m *CanOpenChannelReply) XXX_DiscardUnknown() { + xxx_messageInfo_CanOpenChannelReply.DiscardUnknown(m) +} + +var xxx_messageInfo_CanOpenChannelReply proto.InternalMessageInfo + +func (m *CanOpenChannelReply) GetAddr() *LightningAddress { + if m != nil { + return m.Addr + } + return nil +} + +type OpenChannelRequest struct { + // / The identity pubkey of the Lightning node + Pubkey string `protobuf:"bytes,1,opt,name=pubkey,proto3" json:"pubkey,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *OpenChannelRequest) Reset() { *m = OpenChannelRequest{} } +func (m *OpenChannelRequest) String() string { return proto.CompactTextString(m) } +func (*OpenChannelRequest) ProtoMessage() {} +func (*OpenChannelRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_lspd_ebfec18e50d1a22e, []int{3} +} +func (m *OpenChannelRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_OpenChannelRequest.Unmarshal(m, b) +} +func (m *OpenChannelRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_OpenChannelRequest.Marshal(b, m, deterministic) +} +func (dst *OpenChannelRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_OpenChannelRequest.Merge(dst, src) +} +func (m *OpenChannelRequest) XXX_Size() int { + return xxx_messageInfo_OpenChannelRequest.Size(m) +} +func (m *OpenChannelRequest) XXX_DiscardUnknown() { + xxx_messageInfo_OpenChannelRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_OpenChannelRequest proto.InternalMessageInfo + +func (m *OpenChannelRequest) GetPubkey() string { + if m != nil { + return m.Pubkey + } + return "" +} + +type OpenChannelReply struct { + // / The transaction hash + TxHash string `protobuf:"bytes,1,opt,name=tx_hash,proto3" json:"tx_hash,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *OpenChannelReply) Reset() { *m = OpenChannelReply{} } +func (m *OpenChannelReply) String() string { return proto.CompactTextString(m) } +func (*OpenChannelReply) ProtoMessage() {} +func (*OpenChannelReply) Descriptor() ([]byte, []int) { + return fileDescriptor_lspd_ebfec18e50d1a22e, []int{4} +} +func (m *OpenChannelReply) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_OpenChannelReply.Unmarshal(m, b) +} +func (m *OpenChannelReply) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_OpenChannelReply.Marshal(b, m, deterministic) +} +func (dst *OpenChannelReply) XXX_Merge(src proto.Message) { + xxx_messageInfo_OpenChannelReply.Merge(dst, src) +} +func (m *OpenChannelReply) XXX_Size() int { + return xxx_messageInfo_OpenChannelReply.Size(m) +} +func (m *OpenChannelReply) XXX_DiscardUnknown() { + xxx_messageInfo_OpenChannelReply.DiscardUnknown(m) +} + +var xxx_messageInfo_OpenChannelReply proto.InternalMessageInfo + +func (m *OpenChannelReply) GetTxHash() string { + if m != nil { + return m.TxHash + } + return "" +} + +func init() { + proto.RegisterType((*CanOpenChannelRequest)(nil), "lspd.CanOpenChannelRequest") + proto.RegisterType((*LightningAddress)(nil), "lspd.LightningAddress") + proto.RegisterType((*CanOpenChannelReply)(nil), "lspd.CanOpenChannelReply") + proto.RegisterType((*OpenChannelRequest)(nil), "lspd.OpenChannelRequest") + proto.RegisterType((*OpenChannelReply)(nil), "lspd.OpenChannelReply") +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// ChannelOpenerClient is the client API for ChannelOpener service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type ChannelOpenerClient interface { + CanOpenChannel(ctx context.Context, in *CanOpenChannelRequest, opts ...grpc.CallOption) (*CanOpenChannelReply, error) + OpenChannel(ctx context.Context, in *OpenChannelRequest, opts ...grpc.CallOption) (*OpenChannelReply, error) +} + +type channelOpenerClient struct { + cc *grpc.ClientConn +} + +func NewChannelOpenerClient(cc *grpc.ClientConn) ChannelOpenerClient { + return &channelOpenerClient{cc} +} + +func (c *channelOpenerClient) CanOpenChannel(ctx context.Context, in *CanOpenChannelRequest, opts ...grpc.CallOption) (*CanOpenChannelReply, error) { + out := new(CanOpenChannelReply) + err := c.cc.Invoke(ctx, "/lspd.ChannelOpener/CanOpenChannel", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *channelOpenerClient) OpenChannel(ctx context.Context, in *OpenChannelRequest, opts ...grpc.CallOption) (*OpenChannelReply, error) { + out := new(OpenChannelReply) + err := c.cc.Invoke(ctx, "/lspd.ChannelOpener/OpenChannel", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// ChannelOpenerServer is the server API for ChannelOpener service. +type ChannelOpenerServer interface { + CanOpenChannel(context.Context, *CanOpenChannelRequest) (*CanOpenChannelReply, error) + OpenChannel(context.Context, *OpenChannelRequest) (*OpenChannelReply, error) +} + +func RegisterChannelOpenerServer(s *grpc.Server, srv ChannelOpenerServer) { + s.RegisterService(&_ChannelOpener_serviceDesc, srv) +} + +func _ChannelOpener_CanOpenChannel_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CanOpenChannelRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ChannelOpenerServer).CanOpenChannel(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/lspd.ChannelOpener/CanOpenChannel", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ChannelOpenerServer).CanOpenChannel(ctx, req.(*CanOpenChannelRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _ChannelOpener_OpenChannel_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(OpenChannelRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ChannelOpenerServer).OpenChannel(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/lspd.ChannelOpener/OpenChannel", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ChannelOpenerServer).OpenChannel(ctx, req.(*OpenChannelRequest)) + } + return interceptor(ctx, in, info, handler) +} + +var _ChannelOpener_serviceDesc = grpc.ServiceDesc{ + ServiceName: "lspd.ChannelOpener", + HandlerType: (*ChannelOpenerServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "CanOpenChannel", + Handler: _ChannelOpener_CanOpenChannel_Handler, + }, + { + MethodName: "OpenChannel", + Handler: _ChannelOpener_OpenChannel_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "lspd.proto", +} + +func init() { proto.RegisterFile("lspd.proto", fileDescriptor_lspd_ebfec18e50d1a22e) } + +var fileDescriptor_lspd_ebfec18e50d1a22e = []byte{ + // 261 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x91, 0xc1, 0x4a, 0xc3, 0x40, + 0x10, 0x86, 0x8d, 0x84, 0x4a, 0xa7, 0x28, 0x65, 0xd4, 0x10, 0xf5, 0x22, 0xeb, 0x45, 0xa4, 0x44, + 0xa8, 0x77, 0x21, 0xed, 0x4d, 0x0a, 0x96, 0xbc, 0x80, 0xa4, 0xdd, 0x21, 0x1b, 0x0c, 0x9b, 0x75, + 0x77, 0x2b, 0xe6, 0x65, 0x7c, 0x56, 0xc9, 0x26, 0x82, 0xc6, 0xf5, 0xd0, 0xdb, 0xcc, 0xce, 0xb7, + 0xff, 0xff, 0x33, 0x03, 0x50, 0x19, 0xc5, 0x13, 0xa5, 0x6b, 0x5b, 0x63, 0xd8, 0xd6, 0xec, 0x1e, + 0xce, 0x97, 0xb9, 0x7c, 0x56, 0x24, 0x97, 0x22, 0x97, 0x92, 0xaa, 0x8c, 0xde, 0x76, 0x64, 0x2c, + 0x46, 0x30, 0x52, 0xbb, 0xcd, 0x2b, 0x35, 0x71, 0x70, 0x1d, 0xdc, 0x8e, 0xb3, 0xbe, 0x63, 0x8f, + 0x30, 0x5d, 0x95, 0x85, 0xb0, 0xb2, 0x94, 0x45, 0xca, 0xb9, 0x26, 0x63, 0xfe, 0x63, 0x11, 0x21, + 0x14, 0xb5, 0xb1, 0xf1, 0xa1, 0x7b, 0x75, 0x35, 0x4b, 0xe1, 0x74, 0x68, 0xa8, 0xaa, 0x06, 0xef, + 0x20, 0xcc, 0x39, 0xd7, 0x4e, 0x60, 0x32, 0x8f, 0x12, 0x17, 0x74, 0x68, 0x94, 0x39, 0x86, 0xcd, + 0x00, 0xf7, 0x08, 0x3c, 0x83, 0xe9, 0x1f, 0xb7, 0x18, 0x8e, 0xec, 0xc7, 0x8b, 0xc8, 0x8d, 0xe8, + 0xe1, 0xef, 0x76, 0xfe, 0x19, 0xc0, 0x71, 0x8f, 0xb6, 0xbf, 0x48, 0xe3, 0x13, 0x9c, 0xfc, 0x0e, + 0x8c, 0x57, 0x5d, 0x3a, 0xef, 0xde, 0x2e, 0x2f, 0xfc, 0x43, 0x55, 0x35, 0xec, 0x00, 0x53, 0x98, + 0xfc, 0x14, 0x8a, 0x3b, 0xd6, 0xa3, 0x12, 0x79, 0x26, 0x4e, 0x62, 0x71, 0x03, 0x67, 0x65, 0x9d, + 0x14, 0x5a, 0x6d, 0x3b, 0xc4, 0x90, 0x7e, 0x2f, 0xb7, 0xb4, 0x18, 0xaf, 0x8c, 0xe2, 0xeb, 0xf6, + 0xb2, 0xeb, 0x60, 0x33, 0x72, 0x27, 0x7e, 0xf8, 0x0a, 0x00, 0x00, 0xff, 0xff, 0x6b, 0x54, 0x55, + 0x60, 0xf0, 0x01, 0x00, 0x00, +} diff --git a/rpc/lspd.proto b/rpc/lspd.proto new file mode 100644 index 0000000..f324608 --- /dev/null +++ b/rpc/lspd.proto @@ -0,0 +1,38 @@ +syntax = "proto3"; + +option java_multiple_files = true; +option java_package = "io.grpc.lspd.service"; +option java_outer_classname = "LspdProto"; + +package lspd; + +service ChannelOpener { + rpc CanOpenChannel (CanOpenChannelRequest) returns (CanOpenChannelReply) {} + rpc OpenChannel (OpenChannelRequest) returns (OpenChannelReply) {} +} + +message CanOpenChannelRequest { + /// The identity pubkey of the Lightning node + string pubkey = 1 [json_name = "pubkey"]; +} + +message LightningAddress { + /// The identity pubkey of the Lightning node + string pubkey = 1 [json_name = "pubkey"]; + /// The network location of the lightning node, e.g. `12.34.56.78:9012` or `localhost:10011` + string host = 2 [json_name = "host"]; +} +message CanOpenChannelReply { + /// Lightning address of the peer, in the format `@host` + LightningAddress addr = 1; +} + +message OpenChannelRequest { + /// The identity pubkey of the Lightning node + string pubkey = 1 [json_name = "pubkey"]; +} + +message OpenChannelReply { + /// The transaction hash + string tx_hash = 1 [ json_name = "tx_hash" ]; +} diff --git a/sample.env b/sample.env new file mode 100644 index 0000000..42348ca --- /dev/null +++ b/sample.env @@ -0,0 +1,8 @@ +LISTEN_ADDRESS= + +LND_ADDRESS= +LND_CERT= #replace each eol by \\n +LND_MACAROON_HEX= + +NODE_PUBKEY= +NODE_HOST= \ No newline at end of file diff --git a/server.go b/server.go new file mode 100644 index 0000000..288d53e --- /dev/null +++ b/server.go @@ -0,0 +1,143 @@ +package main + +import ( + "context" + "crypto/x509" + "encoding/hex" + "log" + "net" + "os" + "strings" + + lspd "lspd/rpc" + + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/lightningnetwork/lnd/lnrpc" + "golang.org/x/sync/singleflight" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" + "google.golang.org/grpc/metadata" +) + +const ( + channelAmount = 1000000 +) + +type server struct{} + +var ( + client lnrpc.LightningClient + openChannelReqGroup singleflight.Group +) + +func (s *server) CanOpenChannel(ctx context.Context, in *lspd.CanOpenChannelRequest) (*lspd.CanOpenChannelReply, error) { + return &lspd.CanOpenChannelReply{ + Addr: &lspd.LightningAddress{ + Pubkey: os.Getenv("NODE_PUBKEY"), + Host: os.Getenv("NODE_HOST"), + }, + }, nil +} + +func (s *server) OpenChannel(ctx context.Context, in *lspd.OpenChannelRequest) (*lspd.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 + } + var txidStr string + if len(nodeChannels) == 0 && len(pendingChannels) == 0 { + response, err := client.OpenChannelSync(clientCtx, &lnrpc.OpenChannelRequest{ + LocalFundingAmount: channelAmount, + NodePubkeyString: in.Pubkey, + PushSat: 0, + TargetConf: 1, + MinHtlcMsat: 600, + Private: true, + }) + 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()) + + // don't fail the request in case we can't format the channel id from + // some reason... + if txid != nil { + txidStr = txid.String() + } + } + return &lspd.OpenChannelReply{TxHash: txidStr}, nil + }) + + if err != nil { + return nil, err + } + return r.(*lspd.OpenChannelReply), err +} + +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() { + lis, err := net.Listen("tcp", os.Getenv("LISTEN_ADDRESS")) + 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) + + s := grpc.NewServer() + lspd.RegisterChannelOpenerServer(s, &server{}) + if err := s.Serve(lis); err != nil { + log.Fatalf("failed to serve: %v", err) + } +} From 1cd9ecac5c919654605c339ee4bfc87985fc53c1 Mon Sep 17 00:00:00 2001 From: Yaacov Akiba Slama Date: Wed, 17 Jul 2019 17:49:15 +0300 Subject: [PATCH 002/214] Use full module name --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index e0c192e..8d9b40e 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module lspd +module github.com/breez/lspd go 1.12 From 5e48f77ab363289a14c87e88dc76b5113816b839 Mon Sep 17 00:00:00 2001 From: Yaacov Akiba Slama Date: Wed, 17 Jul 2019 17:51:57 +0300 Subject: [PATCH 003/214] Change rpc package name --- server.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/server.go b/server.go index 288d53e..391cc2c 100644 --- a/server.go +++ b/server.go @@ -9,7 +9,7 @@ import ( "os" "strings" - lspd "lspd/rpc" + lspdrpc "github.com/breez/lspd/rpc" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/lightningnetwork/lnd/lnrpc" @@ -30,16 +30,16 @@ var ( openChannelReqGroup singleflight.Group ) -func (s *server) CanOpenChannel(ctx context.Context, in *lspd.CanOpenChannelRequest) (*lspd.CanOpenChannelReply, error) { - return &lspd.CanOpenChannelReply{ - Addr: &lspd.LightningAddress{ +func (s *server) CanOpenChannel(ctx context.Context, in *lspdrpc.CanOpenChannelRequest) (*lspdrpc.CanOpenChannelReply, error) { + return &lspdrpc.CanOpenChannelReply{ + Addr: &lspdrpc.LightningAddress{ Pubkey: os.Getenv("NODE_PUBKEY"), Host: os.Getenv("NODE_HOST"), }, }, nil } -func (s *server) OpenChannel(ctx context.Context, in *lspd.OpenChannelRequest) (*lspd.OpenChannelReply, error) { +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) @@ -75,13 +75,13 @@ func (s *server) OpenChannel(ctx context.Context, in *lspd.OpenChannelRequest) ( txidStr = txid.String() } } - return &lspd.OpenChannelReply{TxHash: txidStr}, nil + return &lspdrpc.OpenChannelReply{TxHash: txidStr}, nil }) if err != nil { return nil, err } - return r.(*lspd.OpenChannelReply), err + return r.(*lspdrpc.OpenChannelReply), err } func getNodeChannels(nodeID string) ([]*lnrpc.Channel, error) { @@ -136,7 +136,7 @@ func main() { client = lnrpc.NewLightningClient(conn) s := grpc.NewServer() - lspd.RegisterChannelOpenerServer(s, &server{}) + lspdrpc.RegisterChannelOpenerServer(s, &server{}) if err := s.Serve(lis); err != nil { log.Fatalf("failed to serve: %v", err) } From 79d230fd6af1fecb5eec311b819cff53a567f2c8 Mon Sep 17 00:00:00 2001 From: Yaacov Akiba Slama Date: Thu, 18 Jul 2019 16:52:38 +0300 Subject: [PATCH 004/214] Rename CanOpenChannel to ChannelInformation and add other info --- rpc/lspd.pb.go | 251 +++++++++++++++++++++++++++---------------------- rpc/lspd.proto | 31 ++++-- sample.env | 1 + server.go | 17 ++-- 4 files changed, 174 insertions(+), 126 deletions(-) diff --git a/rpc/lspd.pb.go b/rpc/lspd.pb.go index bcb2f35..72c9c4f 100644 --- a/rpc/lspd.pb.go +++ b/rpc/lspd.pb.go @@ -23,7 +23,7 @@ var _ = math.Inf // proto package needs to be updated. const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package -type CanOpenChannelRequest struct { +type ChannelInformationRequest struct { // / The identity pubkey of the Lightning node Pubkey string `protobuf:"bytes,1,opt,name=pubkey,proto3" json:"pubkey,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` @@ -31,122 +31,146 @@ type CanOpenChannelRequest struct { XXX_sizecache int32 `json:"-"` } -func (m *CanOpenChannelRequest) Reset() { *m = CanOpenChannelRequest{} } -func (m *CanOpenChannelRequest) String() string { return proto.CompactTextString(m) } -func (*CanOpenChannelRequest) ProtoMessage() {} -func (*CanOpenChannelRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_lspd_ebfec18e50d1a22e, []int{0} +func (m *ChannelInformationRequest) Reset() { *m = ChannelInformationRequest{} } +func (m *ChannelInformationRequest) String() string { return proto.CompactTextString(m) } +func (*ChannelInformationRequest) ProtoMessage() {} +func (*ChannelInformationRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_lspd_8942e060a453e94e, []int{0} } -func (m *CanOpenChannelRequest) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_CanOpenChannelRequest.Unmarshal(m, b) +func (m *ChannelInformationRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_ChannelInformationRequest.Unmarshal(m, b) } -func (m *CanOpenChannelRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_CanOpenChannelRequest.Marshal(b, m, deterministic) +func (m *ChannelInformationRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_ChannelInformationRequest.Marshal(b, m, deterministic) } -func (dst *CanOpenChannelRequest) XXX_Merge(src proto.Message) { - xxx_messageInfo_CanOpenChannelRequest.Merge(dst, src) +func (dst *ChannelInformationRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_ChannelInformationRequest.Merge(dst, src) } -func (m *CanOpenChannelRequest) XXX_Size() int { - return xxx_messageInfo_CanOpenChannelRequest.Size(m) +func (m *ChannelInformationRequest) XXX_Size() int { + return xxx_messageInfo_ChannelInformationRequest.Size(m) } -func (m *CanOpenChannelRequest) XXX_DiscardUnknown() { - xxx_messageInfo_CanOpenChannelRequest.DiscardUnknown(m) +func (m *ChannelInformationRequest) XXX_DiscardUnknown() { + xxx_messageInfo_ChannelInformationRequest.DiscardUnknown(m) } -var xxx_messageInfo_CanOpenChannelRequest proto.InternalMessageInfo +var xxx_messageInfo_ChannelInformationRequest proto.InternalMessageInfo -func (m *CanOpenChannelRequest) GetPubkey() string { +func (m *ChannelInformationRequest) GetPubkey() string { if m != nil { return m.Pubkey } return "" } -type LightningAddress struct { +type ChannelInformationReply struct { + // / The name of of lsp + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` // / The identity pubkey of the Lightning node - Pubkey string `protobuf:"bytes,1,opt,name=pubkey,proto3" json:"pubkey,omitempty"` + Pubkey string `protobuf:"bytes,2,opt,name=pubkey,proto3" json:"pubkey,omitempty"` // / The network location of the lightning node, e.g. `12.34.56.78:9012` or `localhost:10011` - Host string `protobuf:"bytes,2,opt,name=host,proto3" json:"host,omitempty"` + Host string `protobuf:"bytes,3,opt,name=host,proto3" json:"host,omitempty"` + // / The channel capacity in satoshis + ChannelCapacity int64 `protobuf:"varint,4,opt,name=channel_capacity,proto3" json:"channel_capacity,omitempty"` + // / The target number of blocks that the funding transaction should be confirmed by. + TargetConf int32 `protobuf:"varint,5,opt,name=target_conf,proto3" json:"target_conf,omitempty"` + // / The base fee charged regardless of the number of milli-satoshis sent. + BaseFeeMsat int64 `protobuf:"varint,6,opt,name=base_fee_msat,proto3" json:"base_fee_msat,omitempty"` + // / The effective fee rate in milli-satoshis. The precision of this value goes up to 6 decimal places, so 1e-6. + FeeRate float64 `protobuf:"fixed64,7,opt,name=fee_rate,proto3" json:"fee_rate,omitempty"` + // / The required timelock delta for HTLCs forwarded over the channel. + TimeLockDelta uint32 `protobuf:"varint,8,opt,name=time_lock_delta,proto3" json:"time_lock_delta,omitempty"` + // / The minimum value in millisatoshi we will require for incoming HTLCs on the channel. + MinHtlcMsat int64 `protobuf:"varint,9,opt,name=min_htlc_msat,proto3" json:"min_htlc_msat,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` } -func (m *LightningAddress) Reset() { *m = LightningAddress{} } -func (m *LightningAddress) String() string { return proto.CompactTextString(m) } -func (*LightningAddress) ProtoMessage() {} -func (*LightningAddress) Descriptor() ([]byte, []int) { - return fileDescriptor_lspd_ebfec18e50d1a22e, []int{1} +func (m *ChannelInformationReply) Reset() { *m = ChannelInformationReply{} } +func (m *ChannelInformationReply) String() string { return proto.CompactTextString(m) } +func (*ChannelInformationReply) ProtoMessage() {} +func (*ChannelInformationReply) Descriptor() ([]byte, []int) { + return fileDescriptor_lspd_8942e060a453e94e, []int{1} } -func (m *LightningAddress) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_LightningAddress.Unmarshal(m, b) +func (m *ChannelInformationReply) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_ChannelInformationReply.Unmarshal(m, b) } -func (m *LightningAddress) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_LightningAddress.Marshal(b, m, deterministic) +func (m *ChannelInformationReply) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_ChannelInformationReply.Marshal(b, m, deterministic) } -func (dst *LightningAddress) XXX_Merge(src proto.Message) { - xxx_messageInfo_LightningAddress.Merge(dst, src) +func (dst *ChannelInformationReply) XXX_Merge(src proto.Message) { + xxx_messageInfo_ChannelInformationReply.Merge(dst, src) } -func (m *LightningAddress) XXX_Size() int { - return xxx_messageInfo_LightningAddress.Size(m) +func (m *ChannelInformationReply) XXX_Size() int { + return xxx_messageInfo_ChannelInformationReply.Size(m) } -func (m *LightningAddress) XXX_DiscardUnknown() { - xxx_messageInfo_LightningAddress.DiscardUnknown(m) +func (m *ChannelInformationReply) XXX_DiscardUnknown() { + xxx_messageInfo_ChannelInformationReply.DiscardUnknown(m) } -var xxx_messageInfo_LightningAddress proto.InternalMessageInfo +var xxx_messageInfo_ChannelInformationReply proto.InternalMessageInfo -func (m *LightningAddress) GetPubkey() string { +func (m *ChannelInformationReply) GetName() string { + if m != nil { + return m.Name + } + return "" +} + +func (m *ChannelInformationReply) GetPubkey() string { if m != nil { return m.Pubkey } return "" } -func (m *LightningAddress) GetHost() string { +func (m *ChannelInformationReply) GetHost() string { if m != nil { return m.Host } return "" } -type CanOpenChannelReply struct { - // / Lightning address of the peer, in the format `@host` - Addr *LightningAddress `protobuf:"bytes,1,opt,name=addr,proto3" json:"addr,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` -} - -func (m *CanOpenChannelReply) Reset() { *m = CanOpenChannelReply{} } -func (m *CanOpenChannelReply) String() string { return proto.CompactTextString(m) } -func (*CanOpenChannelReply) ProtoMessage() {} -func (*CanOpenChannelReply) Descriptor() ([]byte, []int) { - return fileDescriptor_lspd_ebfec18e50d1a22e, []int{2} -} -func (m *CanOpenChannelReply) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_CanOpenChannelReply.Unmarshal(m, b) -} -func (m *CanOpenChannelReply) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_CanOpenChannelReply.Marshal(b, m, deterministic) -} -func (dst *CanOpenChannelReply) XXX_Merge(src proto.Message) { - xxx_messageInfo_CanOpenChannelReply.Merge(dst, src) -} -func (m *CanOpenChannelReply) XXX_Size() int { - return xxx_messageInfo_CanOpenChannelReply.Size(m) -} -func (m *CanOpenChannelReply) XXX_DiscardUnknown() { - xxx_messageInfo_CanOpenChannelReply.DiscardUnknown(m) -} - -var xxx_messageInfo_CanOpenChannelReply proto.InternalMessageInfo - -func (m *CanOpenChannelReply) GetAddr() *LightningAddress { +func (m *ChannelInformationReply) GetChannelCapacity() int64 { if m != nil { - return m.Addr + return m.ChannelCapacity } - return nil + return 0 +} + +func (m *ChannelInformationReply) GetTargetConf() int32 { + if m != nil { + return m.TargetConf + } + return 0 +} + +func (m *ChannelInformationReply) GetBaseFeeMsat() int64 { + if m != nil { + return m.BaseFeeMsat + } + return 0 +} + +func (m *ChannelInformationReply) GetFeeRate() float64 { + if m != nil { + return m.FeeRate + } + return 0 +} + +func (m *ChannelInformationReply) GetTimeLockDelta() uint32 { + if m != nil { + return m.TimeLockDelta + } + return 0 +} + +func (m *ChannelInformationReply) GetMinHtlcMsat() int64 { + if m != nil { + return m.MinHtlcMsat + } + return 0 } type OpenChannelRequest struct { @@ -161,7 +185,7 @@ func (m *OpenChannelRequest) Reset() { *m = OpenChannelRequest{} } func (m *OpenChannelRequest) String() string { return proto.CompactTextString(m) } func (*OpenChannelRequest) ProtoMessage() {} func (*OpenChannelRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_lspd_ebfec18e50d1a22e, []int{3} + return fileDescriptor_lspd_8942e060a453e94e, []int{2} } func (m *OpenChannelRequest) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_OpenChannelRequest.Unmarshal(m, b) @@ -200,7 +224,7 @@ func (m *OpenChannelReply) Reset() { *m = OpenChannelReply{} } func (m *OpenChannelReply) String() string { return proto.CompactTextString(m) } func (*OpenChannelReply) ProtoMessage() {} func (*OpenChannelReply) Descriptor() ([]byte, []int) { - return fileDescriptor_lspd_ebfec18e50d1a22e, []int{4} + return fileDescriptor_lspd_8942e060a453e94e, []int{3} } func (m *OpenChannelReply) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_OpenChannelReply.Unmarshal(m, b) @@ -228,9 +252,8 @@ func (m *OpenChannelReply) GetTxHash() string { } func init() { - proto.RegisterType((*CanOpenChannelRequest)(nil), "lspd.CanOpenChannelRequest") - proto.RegisterType((*LightningAddress)(nil), "lspd.LightningAddress") - proto.RegisterType((*CanOpenChannelReply)(nil), "lspd.CanOpenChannelReply") + proto.RegisterType((*ChannelInformationRequest)(nil), "lspd.ChannelInformationRequest") + proto.RegisterType((*ChannelInformationReply)(nil), "lspd.ChannelInformationReply") proto.RegisterType((*OpenChannelRequest)(nil), "lspd.OpenChannelRequest") proto.RegisterType((*OpenChannelReply)(nil), "lspd.OpenChannelReply") } @@ -247,7 +270,7 @@ const _ = grpc.SupportPackageIsVersion4 // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. type ChannelOpenerClient interface { - CanOpenChannel(ctx context.Context, in *CanOpenChannelRequest, opts ...grpc.CallOption) (*CanOpenChannelReply, error) + ChannelInformation(ctx context.Context, in *ChannelInformationRequest, opts ...grpc.CallOption) (*ChannelInformationReply, error) OpenChannel(ctx context.Context, in *OpenChannelRequest, opts ...grpc.CallOption) (*OpenChannelReply, error) } @@ -259,9 +282,9 @@ func NewChannelOpenerClient(cc *grpc.ClientConn) ChannelOpenerClient { return &channelOpenerClient{cc} } -func (c *channelOpenerClient) CanOpenChannel(ctx context.Context, in *CanOpenChannelRequest, opts ...grpc.CallOption) (*CanOpenChannelReply, error) { - out := new(CanOpenChannelReply) - err := c.cc.Invoke(ctx, "/lspd.ChannelOpener/CanOpenChannel", in, out, opts...) +func (c *channelOpenerClient) ChannelInformation(ctx context.Context, in *ChannelInformationRequest, opts ...grpc.CallOption) (*ChannelInformationReply, error) { + out := new(ChannelInformationReply) + err := c.cc.Invoke(ctx, "/lspd.ChannelOpener/ChannelInformation", in, out, opts...) if err != nil { return nil, err } @@ -279,7 +302,7 @@ func (c *channelOpenerClient) OpenChannel(ctx context.Context, in *OpenChannelRe // ChannelOpenerServer is the server API for ChannelOpener service. type ChannelOpenerServer interface { - CanOpenChannel(context.Context, *CanOpenChannelRequest) (*CanOpenChannelReply, error) + ChannelInformation(context.Context, *ChannelInformationRequest) (*ChannelInformationReply, error) OpenChannel(context.Context, *OpenChannelRequest) (*OpenChannelReply, error) } @@ -287,20 +310,20 @@ func RegisterChannelOpenerServer(s *grpc.Server, srv ChannelOpenerServer) { s.RegisterService(&_ChannelOpener_serviceDesc, srv) } -func _ChannelOpener_CanOpenChannel_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(CanOpenChannelRequest) +func _ChannelOpener_ChannelInformation_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ChannelInformationRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { - return srv.(ChannelOpenerServer).CanOpenChannel(ctx, in) + return srv.(ChannelOpenerServer).ChannelInformation(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/lspd.ChannelOpener/CanOpenChannel", + FullMethod: "/lspd.ChannelOpener/ChannelInformation", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(ChannelOpenerServer).CanOpenChannel(ctx, req.(*CanOpenChannelRequest)) + return srv.(ChannelOpenerServer).ChannelInformation(ctx, req.(*ChannelInformationRequest)) } return interceptor(ctx, in, info, handler) } @@ -328,8 +351,8 @@ var _ChannelOpener_serviceDesc = grpc.ServiceDesc{ HandlerType: (*ChannelOpenerServer)(nil), Methods: []grpc.MethodDesc{ { - MethodName: "CanOpenChannel", - Handler: _ChannelOpener_CanOpenChannel_Handler, + MethodName: "ChannelInformation", + Handler: _ChannelOpener_ChannelInformation_Handler, }, { MethodName: "OpenChannel", @@ -340,25 +363,31 @@ var _ChannelOpener_serviceDesc = grpc.ServiceDesc{ Metadata: "lspd.proto", } -func init() { proto.RegisterFile("lspd.proto", fileDescriptor_lspd_ebfec18e50d1a22e) } +func init() { proto.RegisterFile("lspd.proto", fileDescriptor_lspd_8942e060a453e94e) } -var fileDescriptor_lspd_ebfec18e50d1a22e = []byte{ - // 261 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x91, 0xc1, 0x4a, 0xc3, 0x40, - 0x10, 0x86, 0x8d, 0x84, 0x4a, 0xa7, 0x28, 0x65, 0xd4, 0x10, 0xf5, 0x22, 0xeb, 0x45, 0xa4, 0x44, - 0xa8, 0x77, 0x21, 0xed, 0x4d, 0x0a, 0x96, 0xbc, 0x80, 0xa4, 0xdd, 0x21, 0x1b, 0x0c, 0x9b, 0x75, - 0x77, 0x2b, 0xe6, 0x65, 0x7c, 0x56, 0xc9, 0x26, 0x82, 0xc6, 0xf5, 0xd0, 0xdb, 0xcc, 0xce, 0xb7, - 0xff, 0xff, 0x33, 0x03, 0x50, 0x19, 0xc5, 0x13, 0xa5, 0x6b, 0x5b, 0x63, 0xd8, 0xd6, 0xec, 0x1e, - 0xce, 0x97, 0xb9, 0x7c, 0x56, 0x24, 0x97, 0x22, 0x97, 0x92, 0xaa, 0x8c, 0xde, 0x76, 0x64, 0x2c, - 0x46, 0x30, 0x52, 0xbb, 0xcd, 0x2b, 0x35, 0x71, 0x70, 0x1d, 0xdc, 0x8e, 0xb3, 0xbe, 0x63, 0x8f, - 0x30, 0x5d, 0x95, 0x85, 0xb0, 0xb2, 0x94, 0x45, 0xca, 0xb9, 0x26, 0x63, 0xfe, 0x63, 0x11, 0x21, - 0x14, 0xb5, 0xb1, 0xf1, 0xa1, 0x7b, 0x75, 0x35, 0x4b, 0xe1, 0x74, 0x68, 0xa8, 0xaa, 0x06, 0xef, - 0x20, 0xcc, 0x39, 0xd7, 0x4e, 0x60, 0x32, 0x8f, 0x12, 0x17, 0x74, 0x68, 0x94, 0x39, 0x86, 0xcd, - 0x00, 0xf7, 0x08, 0x3c, 0x83, 0xe9, 0x1f, 0xb7, 0x18, 0x8e, 0xec, 0xc7, 0x8b, 0xc8, 0x8d, 0xe8, - 0xe1, 0xef, 0x76, 0xfe, 0x19, 0xc0, 0x71, 0x8f, 0xb6, 0xbf, 0x48, 0xe3, 0x13, 0x9c, 0xfc, 0x0e, - 0x8c, 0x57, 0x5d, 0x3a, 0xef, 0xde, 0x2e, 0x2f, 0xfc, 0x43, 0x55, 0x35, 0xec, 0x00, 0x53, 0x98, - 0xfc, 0x14, 0x8a, 0x3b, 0xd6, 0xa3, 0x12, 0x79, 0x26, 0x4e, 0x62, 0x71, 0x03, 0x67, 0x65, 0x9d, - 0x14, 0x5a, 0x6d, 0x3b, 0xc4, 0x90, 0x7e, 0x2f, 0xb7, 0xb4, 0x18, 0xaf, 0x8c, 0xe2, 0xeb, 0xf6, - 0xb2, 0xeb, 0x60, 0x33, 0x72, 0x27, 0x7e, 0xf8, 0x0a, 0x00, 0x00, 0xff, 0xff, 0x6b, 0x54, 0x55, - 0x60, 0xf0, 0x01, 0x00, 0x00, +var fileDescriptor_lspd_8942e060a453e94e = []byte{ + // 364 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x84, 0x92, 0xcf, 0x8a, 0xdb, 0x30, + 0x10, 0xc6, 0xab, 0xfc, 0xcf, 0x84, 0xd0, 0x20, 0x4a, 0xaa, 0x06, 0x4a, 0x8d, 0xdb, 0x83, 0x29, + 0x21, 0x87, 0xe6, 0x09, 0x9a, 0x9e, 0x0a, 0x85, 0x06, 0x1f, 0x7a, 0x15, 0x8a, 0x32, 0x89, 0x4d, + 0x6c, 0x49, 0xb5, 0x94, 0x52, 0xbf, 0x52, 0xdf, 0xa0, 0x6f, 0xb7, 0xc8, 0xf6, 0x2e, 0xce, 0x26, + 0x61, 0x6f, 0x33, 0x9f, 0x7f, 0xf3, 0x8d, 0x35, 0x33, 0x00, 0x99, 0x35, 0xfb, 0x95, 0x29, 0xb4, + 0xd3, 0xb4, 0xe7, 0xe3, 0x70, 0x0d, 0xef, 0xbe, 0x25, 0x42, 0x29, 0xcc, 0xbe, 0xab, 0x83, 0x2e, + 0x72, 0xe1, 0x52, 0xad, 0x62, 0xfc, 0x7d, 0x46, 0xeb, 0xe8, 0x1c, 0x06, 0xe6, 0xbc, 0x3b, 0x61, + 0xc9, 0x48, 0x40, 0xa2, 0x71, 0xdc, 0x64, 0xe1, 0xff, 0x0e, 0xbc, 0xbd, 0x55, 0x65, 0xb2, 0x92, + 0x52, 0xe8, 0x29, 0x91, 0x63, 0x53, 0x51, 0xc5, 0x2d, 0x9f, 0x4e, 0xdb, 0xc7, 0xb3, 0x89, 0xb6, + 0x8e, 0x75, 0x6b, 0xd6, 0xc7, 0xf4, 0x33, 0xcc, 0x64, 0x6d, 0xcd, 0xa5, 0x30, 0x42, 0xa6, 0xae, + 0x64, 0xbd, 0x80, 0x44, 0xdd, 0xf8, 0x4a, 0xa7, 0x01, 0x4c, 0x9c, 0x28, 0x8e, 0xe8, 0xb8, 0xd4, + 0xea, 0xc0, 0xfa, 0x01, 0x89, 0xfa, 0x71, 0x5b, 0xa2, 0x9f, 0x60, 0xba, 0x13, 0x16, 0xf9, 0x01, + 0x91, 0xe7, 0x56, 0x38, 0x36, 0xa8, 0xac, 0x2e, 0x45, 0xba, 0x80, 0x91, 0x8f, 0x0b, 0xe1, 0x90, + 0x0d, 0x03, 0x12, 0x91, 0xf8, 0x29, 0xa7, 0x11, 0xbc, 0x76, 0x69, 0x8e, 0x3c, 0xd3, 0xf2, 0xc4, + 0xf7, 0x98, 0x39, 0xc1, 0x46, 0x01, 0x89, 0xa6, 0xf1, 0x73, 0xd9, 0xf7, 0xca, 0x53, 0xc5, 0x13, + 0x97, 0xc9, 0xba, 0xd7, 0xb8, 0xee, 0x75, 0x21, 0x86, 0x4b, 0xa0, 0x3f, 0x0d, 0xaa, 0x66, 0x7c, + 0x2f, 0x4d, 0x7a, 0x09, 0xb3, 0x0b, 0xda, 0x4f, 0x98, 0xc1, 0xd0, 0xfd, 0xe5, 0x89, 0xb0, 0x49, + 0x03, 0x3f, 0xa6, 0x5f, 0xfe, 0x11, 0x98, 0x36, 0xa8, 0xaf, 0xc2, 0x82, 0xfe, 0x02, 0x7a, 0xbd, + 0x28, 0xfa, 0x61, 0x55, 0xdd, 0xc1, 0xdd, 0xc5, 0x2f, 0xde, 0xdf, 0x07, 0x4c, 0x56, 0x86, 0xaf, + 0xe8, 0x57, 0x98, 0xb4, 0xfe, 0x8b, 0xb2, 0x9a, 0xbf, 0x7e, 0xd8, 0x62, 0x7e, 0xe3, 0x4b, 0x65, + 0xb1, 0xf9, 0x08, 0x6f, 0x52, 0xbd, 0x3a, 0x16, 0x46, 0xd6, 0x88, 0xc5, 0xe2, 0x4f, 0x2a, 0x71, + 0x33, 0xfe, 0x61, 0xcd, 0x7e, 0xeb, 0x4f, 0x74, 0x4b, 0x76, 0x83, 0xea, 0x56, 0xd7, 0x0f, 0x01, + 0x00, 0x00, 0xff, 0xff, 0xf5, 0x9a, 0xe0, 0xb2, 0xb9, 0x02, 0x00, 0x00, } diff --git a/rpc/lspd.proto b/rpc/lspd.proto index f324608..7cdabbf 100644 --- a/rpc/lspd.proto +++ b/rpc/lspd.proto @@ -7,24 +7,37 @@ option java_outer_classname = "LspdProto"; package lspd; service ChannelOpener { - rpc CanOpenChannel (CanOpenChannelRequest) returns (CanOpenChannelReply) {} + rpc ChannelInformation (ChannelInformationRequest) returns (ChannelInformationReply) {} rpc OpenChannel (OpenChannelRequest) returns (OpenChannelReply) {} } -message CanOpenChannelRequest { +message ChannelInformationRequest { /// The identity pubkey of the Lightning node string pubkey = 1 [json_name = "pubkey"]; } -message LightningAddress { +message ChannelInformationReply { + /// The name of of lsp + string name = 1 [json_name = "name"]; + /// The identity pubkey of the Lightning node - string pubkey = 1 [json_name = "pubkey"]; + string pubkey = 2 [json_name = "pubkey"]; /// The network location of the lightning node, e.g. `12.34.56.78:9012` or `localhost:10011` - string host = 2 [json_name = "host"]; -} -message CanOpenChannelReply { - /// Lightning address of the peer, in the format `@host` - LightningAddress addr = 1; + string host = 3 [json_name = "host"]; + + /// The channel capacity in satoshis + int64 channel_capacity = 4 [json_name = "channel_capacity"]; + /// The target number of blocks that the funding transaction should be confirmed by. + int32 target_conf = 5 [json_name = "target_conf"]; + + /// The base fee charged regardless of the number of milli-satoshis sent. + int64 base_fee_msat = 6 [json_name = "base_fee_msat"]; + /// The effective fee rate in milli-satoshis. The precision of this value goes up to 6 decimal places, so 1e-6. + double fee_rate = 7 [json_name = "fee_rate"]; + /// The required timelock delta for HTLCs forwarded over the channel. + uint32 time_lock_delta = 8 [json_name = "time_lock_delta"]; + /// The minimum value in millisatoshi we will require for incoming HTLCs on the channel. + int64 min_htlc_msat = 9 [json_name = "min_htlc_msat"]; } message OpenChannelRequest { diff --git a/sample.env b/sample.env index 42348ca..61c2e90 100644 --- a/sample.env +++ b/sample.env @@ -4,5 +4,6 @@ LND_ADDRESS= LND_CERT= #replace each eol by \\n LND_MACAROON_HEX= +NODE_NAME= NODE_PUBKEY= NODE_HOST= \ No newline at end of file diff --git a/server.go b/server.go index 391cc2c..8d9d490 100644 --- a/server.go +++ b/server.go @@ -30,12 +30,17 @@ var ( openChannelReqGroup singleflight.Group ) -func (s *server) CanOpenChannel(ctx context.Context, in *lspdrpc.CanOpenChannelRequest) (*lspdrpc.CanOpenChannelReply, error) { - return &lspdrpc.CanOpenChannelReply{ - Addr: &lspdrpc.LightningAddress{ - Pubkey: os.Getenv("NODE_PUBKEY"), - Host: os.Getenv("NODE_HOST"), - }, +func (s *server) ChannelInformation(ctx context.Context, in *lspdrpc.ChannelInformationRequest) (*lspdrpc.ChannelInformationReply, error) { + return &lspdrpc.ChannelInformationReply{ + Name: os.Getenv("NODE_NAME"), + Pubkey: os.Getenv("NODE_PUBKEY"), + Host: os.Getenv("NODE_HOST"), + ChannelCapacity: channelAmount, + TargetConf: 1, + MinHtlcMsat: 600, + BaseFeeMsat: 1000, + FeeRate: 0.000001, + TimeLockDelta: 144, }, nil } From 93eb57364c3a5e7fa360df7af14522785e70ccaf Mon Sep 17 00:00:00 2001 From: Yaacov Akiba Slama Date: Mon, 22 Jul 2019 14:21:37 +0300 Subject: [PATCH 005/214] Format the proto file --- rpc/lspd.pb.go | 24 ++++++++++++--------- rpc/lspd.proto | 57 +++++++++++++++++++++++++++----------------------- 2 files changed, 45 insertions(+), 36 deletions(-) diff --git a/rpc/lspd.pb.go b/rpc/lspd.pb.go index 72c9c4f..88fd420 100644 --- a/rpc/lspd.pb.go +++ b/rpc/lspd.pb.go @@ -35,7 +35,7 @@ func (m *ChannelInformationRequest) Reset() { *m = ChannelInformationReq func (m *ChannelInformationRequest) String() string { return proto.CompactTextString(m) } func (*ChannelInformationRequest) ProtoMessage() {} func (*ChannelInformationRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_lspd_8942e060a453e94e, []int{0} + return fileDescriptor_lspd_c62fb8bb40946759, []int{0} } func (m *ChannelInformationRequest) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_ChannelInformationRequest.Unmarshal(m, b) @@ -67,19 +67,23 @@ type ChannelInformationReply struct { Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` // / The identity pubkey of the Lightning node Pubkey string `protobuf:"bytes,2,opt,name=pubkey,proto3" json:"pubkey,omitempty"` - // / The network location of the lightning node, e.g. `12.34.56.78:9012` or `localhost:10011` + // / The network location of the lightning node, e.g. `12.34.56.78:9012` or + // / `localhost:10011` Host string `protobuf:"bytes,3,opt,name=host,proto3" json:"host,omitempty"` // / The channel capacity in satoshis ChannelCapacity int64 `protobuf:"varint,4,opt,name=channel_capacity,proto3" json:"channel_capacity,omitempty"` - // / The target number of blocks that the funding transaction should be confirmed by. + // / The target number of blocks that the funding transaction should be + // / confirmed by. TargetConf int32 `protobuf:"varint,5,opt,name=target_conf,proto3" json:"target_conf,omitempty"` // / The base fee charged regardless of the number of milli-satoshis sent. BaseFeeMsat int64 `protobuf:"varint,6,opt,name=base_fee_msat,proto3" json:"base_fee_msat,omitempty"` - // / The effective fee rate in milli-satoshis. The precision of this value goes up to 6 decimal places, so 1e-6. + // / The effective fee rate in milli-satoshis. The precision of this value goes + // / up to 6 decimal places, so 1e-6. FeeRate float64 `protobuf:"fixed64,7,opt,name=fee_rate,proto3" json:"fee_rate,omitempty"` // / The required timelock delta for HTLCs forwarded over the channel. TimeLockDelta uint32 `protobuf:"varint,8,opt,name=time_lock_delta,proto3" json:"time_lock_delta,omitempty"` - // / The minimum value in millisatoshi we will require for incoming HTLCs on the channel. + // / The minimum value in millisatoshi we will require for incoming HTLCs on + // / the channel. MinHtlcMsat int64 `protobuf:"varint,9,opt,name=min_htlc_msat,proto3" json:"min_htlc_msat,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` @@ -90,7 +94,7 @@ func (m *ChannelInformationReply) Reset() { *m = ChannelInformationReply func (m *ChannelInformationReply) String() string { return proto.CompactTextString(m) } func (*ChannelInformationReply) ProtoMessage() {} func (*ChannelInformationReply) Descriptor() ([]byte, []int) { - return fileDescriptor_lspd_8942e060a453e94e, []int{1} + return fileDescriptor_lspd_c62fb8bb40946759, []int{1} } func (m *ChannelInformationReply) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_ChannelInformationReply.Unmarshal(m, b) @@ -185,7 +189,7 @@ func (m *OpenChannelRequest) Reset() { *m = OpenChannelRequest{} } func (m *OpenChannelRequest) String() string { return proto.CompactTextString(m) } func (*OpenChannelRequest) ProtoMessage() {} func (*OpenChannelRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_lspd_8942e060a453e94e, []int{2} + return fileDescriptor_lspd_c62fb8bb40946759, []int{2} } func (m *OpenChannelRequest) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_OpenChannelRequest.Unmarshal(m, b) @@ -224,7 +228,7 @@ func (m *OpenChannelReply) Reset() { *m = OpenChannelReply{} } func (m *OpenChannelReply) String() string { return proto.CompactTextString(m) } func (*OpenChannelReply) ProtoMessage() {} func (*OpenChannelReply) Descriptor() ([]byte, []int) { - return fileDescriptor_lspd_8942e060a453e94e, []int{3} + return fileDescriptor_lspd_c62fb8bb40946759, []int{3} } func (m *OpenChannelReply) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_OpenChannelReply.Unmarshal(m, b) @@ -363,9 +367,9 @@ var _ChannelOpener_serviceDesc = grpc.ServiceDesc{ Metadata: "lspd.proto", } -func init() { proto.RegisterFile("lspd.proto", fileDescriptor_lspd_8942e060a453e94e) } +func init() { proto.RegisterFile("lspd.proto", fileDescriptor_lspd_c62fb8bb40946759) } -var fileDescriptor_lspd_8942e060a453e94e = []byte{ +var fileDescriptor_lspd_c62fb8bb40946759 = []byte{ // 364 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x84, 0x92, 0xcf, 0x8a, 0xdb, 0x30, 0x10, 0xc6, 0xab, 0xfc, 0xcf, 0x84, 0xd0, 0x20, 0x4a, 0xaa, 0x06, 0x4a, 0x8d, 0xdb, 0x83, 0x29, diff --git a/rpc/lspd.proto b/rpc/lspd.proto index 7cdabbf..42a7c75 100644 --- a/rpc/lspd.proto +++ b/rpc/lspd.proto @@ -7,45 +7,50 @@ option java_outer_classname = "LspdProto"; package lspd; service ChannelOpener { - rpc ChannelInformation (ChannelInformationRequest) returns (ChannelInformationReply) {} - rpc OpenChannel (OpenChannelRequest) returns (OpenChannelReply) {} + rpc ChannelInformation(ChannelInformationRequest) + returns (ChannelInformationReply) {} + rpc OpenChannel(OpenChannelRequest) returns (OpenChannelReply) {} } message ChannelInformationRequest { - /// The identity pubkey of the Lightning node - string pubkey = 1 [json_name = "pubkey"]; + /// The identity pubkey of the Lightning node + string pubkey = 1 [ json_name = "pubkey" ]; } message ChannelInformationReply { - /// The name of of lsp - string name = 1 [json_name = "name"]; + /// The name of of lsp + string name = 1 [ json_name = "name" ]; - /// The identity pubkey of the Lightning node - string pubkey = 2 [json_name = "pubkey"]; - /// The network location of the lightning node, e.g. `12.34.56.78:9012` or `localhost:10011` - string host = 3 [json_name = "host"]; + /// The identity pubkey of the Lightning node + string pubkey = 2 [ json_name = "pubkey" ]; + /// The network location of the lightning node, e.g. `12.34.56.78:9012` or + /// `localhost:10011` + string host = 3 [ json_name = "host" ]; - /// The channel capacity in satoshis - int64 channel_capacity = 4 [json_name = "channel_capacity"]; - /// The target number of blocks that the funding transaction should be confirmed by. - int32 target_conf = 5 [json_name = "target_conf"]; + /// The channel capacity in satoshis + int64 channel_capacity = 4 [ json_name = "channel_capacity" ]; + /// The target number of blocks that the funding transaction should be + /// confirmed by. + int32 target_conf = 5 [ json_name = "target_conf" ]; - /// The base fee charged regardless of the number of milli-satoshis sent. - int64 base_fee_msat = 6 [json_name = "base_fee_msat"]; - /// The effective fee rate in milli-satoshis. The precision of this value goes up to 6 decimal places, so 1e-6. - double fee_rate = 7 [json_name = "fee_rate"]; - /// The required timelock delta for HTLCs forwarded over the channel. - uint32 time_lock_delta = 8 [json_name = "time_lock_delta"]; - /// The minimum value in millisatoshi we will require for incoming HTLCs on the channel. - int64 min_htlc_msat = 9 [json_name = "min_htlc_msat"]; + /// The base fee charged regardless of the number of milli-satoshis sent. + int64 base_fee_msat = 6 [ json_name = "base_fee_msat" ]; + /// The effective fee rate in milli-satoshis. The precision of this value goes + /// up to 6 decimal places, so 1e-6. + double fee_rate = 7 [ json_name = "fee_rate" ]; + /// The required timelock delta for HTLCs forwarded over the channel. + uint32 time_lock_delta = 8 [ json_name = "time_lock_delta" ]; + /// The minimum value in millisatoshi we will require for incoming HTLCs on + /// the channel. + int64 min_htlc_msat = 9 [ json_name = "min_htlc_msat" ]; } message OpenChannelRequest { - /// The identity pubkey of the Lightning node - string pubkey = 1 [json_name = "pubkey"]; + /// The identity pubkey of the Lightning node + string pubkey = 1 [ json_name = "pubkey" ]; } message OpenChannelReply { - /// The transaction hash - string tx_hash = 1 [ json_name = "tx_hash" ]; + /// The transaction hash + string tx_hash = 1 [ json_name = "tx_hash" ]; } From ab824d1565745dd436e6527ffde61a00c42e61fc Mon Sep 17 00:00:00 2001 From: Yaacov Akiba Slama Date: Mon, 22 Jul 2019 15:12:35 +0300 Subject: [PATCH 006/214] Add output index to OpenChannelReply --- rpc/lspd.pb.go | 72 ++++++++++++++++++++++++++++---------------------- rpc/lspd.proto | 2 ++ server.go | 5 ++-- 3 files changed, 46 insertions(+), 33 deletions(-) diff --git a/rpc/lspd.pb.go b/rpc/lspd.pb.go index 88fd420..69793b9 100644 --- a/rpc/lspd.pb.go +++ b/rpc/lspd.pb.go @@ -35,7 +35,7 @@ func (m *ChannelInformationRequest) Reset() { *m = ChannelInformationReq func (m *ChannelInformationRequest) String() string { return proto.CompactTextString(m) } func (*ChannelInformationRequest) ProtoMessage() {} func (*ChannelInformationRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_lspd_c62fb8bb40946759, []int{0} + return fileDescriptor_lspd_f6548acf9d60fb70, []int{0} } func (m *ChannelInformationRequest) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_ChannelInformationRequest.Unmarshal(m, b) @@ -94,7 +94,7 @@ func (m *ChannelInformationReply) Reset() { *m = ChannelInformationReply func (m *ChannelInformationReply) String() string { return proto.CompactTextString(m) } func (*ChannelInformationReply) ProtoMessage() {} func (*ChannelInformationReply) Descriptor() ([]byte, []int) { - return fileDescriptor_lspd_c62fb8bb40946759, []int{1} + return fileDescriptor_lspd_f6548acf9d60fb70, []int{1} } func (m *ChannelInformationReply) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_ChannelInformationReply.Unmarshal(m, b) @@ -189,7 +189,7 @@ func (m *OpenChannelRequest) Reset() { *m = OpenChannelRequest{} } func (m *OpenChannelRequest) String() string { return proto.CompactTextString(m) } func (*OpenChannelRequest) ProtoMessage() {} func (*OpenChannelRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_lspd_c62fb8bb40946759, []int{2} + return fileDescriptor_lspd_f6548acf9d60fb70, []int{2} } func (m *OpenChannelRequest) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_OpenChannelRequest.Unmarshal(m, b) @@ -218,7 +218,9 @@ func (m *OpenChannelRequest) GetPubkey() string { type OpenChannelReply struct { // / The transaction hash - TxHash string `protobuf:"bytes,1,opt,name=tx_hash,proto3" json:"tx_hash,omitempty"` + TxHash string `protobuf:"bytes,1,opt,name=tx_hash,proto3" json:"tx_hash,omitempty"` + // / The output index + OutputIndex uint32 `protobuf:"varint,2,opt,name=output_index,proto3" json:"output_index,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` @@ -228,7 +230,7 @@ func (m *OpenChannelReply) Reset() { *m = OpenChannelReply{} } func (m *OpenChannelReply) String() string { return proto.CompactTextString(m) } func (*OpenChannelReply) ProtoMessage() {} func (*OpenChannelReply) Descriptor() ([]byte, []int) { - return fileDescriptor_lspd_c62fb8bb40946759, []int{3} + return fileDescriptor_lspd_f6548acf9d60fb70, []int{3} } func (m *OpenChannelReply) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_OpenChannelReply.Unmarshal(m, b) @@ -255,6 +257,13 @@ func (m *OpenChannelReply) GetTxHash() string { return "" } +func (m *OpenChannelReply) GetOutputIndex() uint32 { + if m != nil { + return m.OutputIndex + } + return 0 +} + func init() { proto.RegisterType((*ChannelInformationRequest)(nil), "lspd.ChannelInformationRequest") proto.RegisterType((*ChannelInformationReply)(nil), "lspd.ChannelInformationReply") @@ -367,31 +376,32 @@ var _ChannelOpener_serviceDesc = grpc.ServiceDesc{ Metadata: "lspd.proto", } -func init() { proto.RegisterFile("lspd.proto", fileDescriptor_lspd_c62fb8bb40946759) } +func init() { proto.RegisterFile("lspd.proto", fileDescriptor_lspd_f6548acf9d60fb70) } -var fileDescriptor_lspd_c62fb8bb40946759 = []byte{ - // 364 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x84, 0x92, 0xcf, 0x8a, 0xdb, 0x30, - 0x10, 0xc6, 0xab, 0xfc, 0xcf, 0x84, 0xd0, 0x20, 0x4a, 0xaa, 0x06, 0x4a, 0x8d, 0xdb, 0x83, 0x29, - 0x21, 0x87, 0xe6, 0x09, 0x9a, 0x9e, 0x0a, 0x85, 0x06, 0x1f, 0x7a, 0x15, 0x8a, 0x32, 0x89, 0x4d, - 0x6c, 0x49, 0xb5, 0x94, 0x52, 0xbf, 0x52, 0xdf, 0xa0, 0x6f, 0xb7, 0xc8, 0xf6, 0x2e, 0xce, 0x26, - 0x61, 0x6f, 0x33, 0x9f, 0x7f, 0xf3, 0x8d, 0x35, 0x33, 0x00, 0x99, 0x35, 0xfb, 0x95, 0x29, 0xb4, - 0xd3, 0xb4, 0xe7, 0xe3, 0x70, 0x0d, 0xef, 0xbe, 0x25, 0x42, 0x29, 0xcc, 0xbe, 0xab, 0x83, 0x2e, - 0x72, 0xe1, 0x52, 0xad, 0x62, 0xfc, 0x7d, 0x46, 0xeb, 0xe8, 0x1c, 0x06, 0xe6, 0xbc, 0x3b, 0x61, - 0xc9, 0x48, 0x40, 0xa2, 0x71, 0xdc, 0x64, 0xe1, 0xff, 0x0e, 0xbc, 0xbd, 0x55, 0x65, 0xb2, 0x92, - 0x52, 0xe8, 0x29, 0x91, 0x63, 0x53, 0x51, 0xc5, 0x2d, 0x9f, 0x4e, 0xdb, 0xc7, 0xb3, 0x89, 0xb6, - 0x8e, 0x75, 0x6b, 0xd6, 0xc7, 0xf4, 0x33, 0xcc, 0x64, 0x6d, 0xcd, 0xa5, 0x30, 0x42, 0xa6, 0xae, - 0x64, 0xbd, 0x80, 0x44, 0xdd, 0xf8, 0x4a, 0xa7, 0x01, 0x4c, 0x9c, 0x28, 0x8e, 0xe8, 0xb8, 0xd4, - 0xea, 0xc0, 0xfa, 0x01, 0x89, 0xfa, 0x71, 0x5b, 0xa2, 0x9f, 0x60, 0xba, 0x13, 0x16, 0xf9, 0x01, - 0x91, 0xe7, 0x56, 0x38, 0x36, 0xa8, 0xac, 0x2e, 0x45, 0xba, 0x80, 0x91, 0x8f, 0x0b, 0xe1, 0x90, - 0x0d, 0x03, 0x12, 0x91, 0xf8, 0x29, 0xa7, 0x11, 0xbc, 0x76, 0x69, 0x8e, 0x3c, 0xd3, 0xf2, 0xc4, - 0xf7, 0x98, 0x39, 0xc1, 0x46, 0x01, 0x89, 0xa6, 0xf1, 0x73, 0xd9, 0xf7, 0xca, 0x53, 0xc5, 0x13, - 0x97, 0xc9, 0xba, 0xd7, 0xb8, 0xee, 0x75, 0x21, 0x86, 0x4b, 0xa0, 0x3f, 0x0d, 0xaa, 0x66, 0x7c, - 0x2f, 0x4d, 0x7a, 0x09, 0xb3, 0x0b, 0xda, 0x4f, 0x98, 0xc1, 0xd0, 0xfd, 0xe5, 0x89, 0xb0, 0x49, - 0x03, 0x3f, 0xa6, 0x5f, 0xfe, 0x11, 0x98, 0x36, 0xa8, 0xaf, 0xc2, 0x82, 0xfe, 0x02, 0x7a, 0xbd, - 0x28, 0xfa, 0x61, 0x55, 0xdd, 0xc1, 0xdd, 0xc5, 0x2f, 0xde, 0xdf, 0x07, 0x4c, 0x56, 0x86, 0xaf, - 0xe8, 0x57, 0x98, 0xb4, 0xfe, 0x8b, 0xb2, 0x9a, 0xbf, 0x7e, 0xd8, 0x62, 0x7e, 0xe3, 0x4b, 0x65, - 0xb1, 0xf9, 0x08, 0x6f, 0x52, 0xbd, 0x3a, 0x16, 0x46, 0xd6, 0x88, 0xc5, 0xe2, 0x4f, 0x2a, 0x71, - 0x33, 0xfe, 0x61, 0xcd, 0x7e, 0xeb, 0x4f, 0x74, 0x4b, 0x76, 0x83, 0xea, 0x56, 0xd7, 0x0f, 0x01, - 0x00, 0x00, 0xff, 0xff, 0xf5, 0x9a, 0xe0, 0xb2, 0xb9, 0x02, 0x00, 0x00, +var fileDescriptor_lspd_f6548acf9d60fb70 = []byte{ + // 381 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x84, 0x52, 0xc1, 0x6e, 0xd4, 0x30, + 0x10, 0xc5, 0xed, 0x76, 0xdb, 0x9d, 0x12, 0x51, 0x8d, 0x50, 0x31, 0x2b, 0x21, 0xa2, 0xc0, 0x21, + 0x42, 0x68, 0x0f, 0xf4, 0x0b, 0x28, 0x27, 0x24, 0x24, 0x56, 0x39, 0x70, 0xb5, 0xbc, 0xce, 0x6c, + 0x13, 0x35, 0xb1, 0x4d, 0xec, 0xa0, 0xe6, 0x97, 0xf8, 0x03, 0xfe, 0x0e, 0x39, 0x09, 0x28, 0x61, + 0xbb, 0xe2, 0xf6, 0xe6, 0xe5, 0xcd, 0x9b, 0xe4, 0xe5, 0x01, 0x54, 0xce, 0xe6, 0x1b, 0xdb, 0x18, + 0x6f, 0x70, 0x11, 0x70, 0x72, 0x03, 0x2f, 0x3f, 0x15, 0x52, 0x6b, 0xaa, 0x3e, 0xeb, 0xbd, 0x69, + 0x6a, 0xe9, 0x4b, 0xa3, 0x33, 0xfa, 0xde, 0x92, 0xf3, 0x78, 0x0d, 0x4b, 0xdb, 0xee, 0xee, 0xa9, + 0xe3, 0x2c, 0x66, 0xe9, 0x2a, 0x1b, 0xa7, 0xe4, 0xd7, 0x09, 0xbc, 0x78, 0x6c, 0xcb, 0x56, 0x1d, + 0x22, 0x2c, 0xb4, 0xac, 0x69, 0xdc, 0xe8, 0xf1, 0xc4, 0xe7, 0x64, 0xea, 0x13, 0xb4, 0x85, 0x71, + 0x9e, 0x9f, 0x0e, 0xda, 0x80, 0xf1, 0x1d, 0x5c, 0xa9, 0xc1, 0x5a, 0x28, 0x69, 0xa5, 0x2a, 0x7d, + 0xc7, 0x17, 0x31, 0x4b, 0x4f, 0xb3, 0x03, 0x1e, 0x63, 0xb8, 0xf4, 0xb2, 0xb9, 0x23, 0x2f, 0x94, + 0xd1, 0x7b, 0x7e, 0x16, 0xb3, 0xf4, 0x2c, 0x9b, 0x52, 0xf8, 0x16, 0xa2, 0x9d, 0x74, 0x24, 0xf6, + 0x44, 0xa2, 0x76, 0xd2, 0xf3, 0x65, 0x6f, 0x35, 0x27, 0x71, 0x0d, 0x17, 0x01, 0x37, 0xd2, 0x13, + 0x3f, 0x8f, 0x59, 0xca, 0xb2, 0xbf, 0x33, 0xa6, 0xf0, 0xcc, 0x97, 0x35, 0x89, 0xca, 0xa8, 0x7b, + 0x91, 0x53, 0xe5, 0x25, 0xbf, 0x88, 0x59, 0x1a, 0x65, 0xff, 0xd2, 0xe1, 0x56, 0x5d, 0x6a, 0x51, + 0xf8, 0x4a, 0x0d, 0xb7, 0x56, 0xc3, 0xad, 0x19, 0x99, 0xbc, 0x07, 0xfc, 0x6a, 0x49, 0x8f, 0xf1, + 0xfd, 0x2f, 0xe9, 0x2d, 0x5c, 0xcd, 0xd4, 0x21, 0x61, 0x0e, 0xe7, 0xfe, 0x41, 0x14, 0xd2, 0x15, + 0xa3, 0xf8, 0xcf, 0x88, 0x09, 0x3c, 0x35, 0xad, 0xb7, 0xad, 0x17, 0xa5, 0xce, 0xe9, 0xa1, 0x4f, + 0x3b, 0xca, 0x66, 0xdc, 0x87, 0x9f, 0x0c, 0xa2, 0xd1, 0x2e, 0x38, 0x53, 0x83, 0xdf, 0x00, 0x0f, + 0x7f, 0x26, 0xbe, 0xde, 0xf4, 0x5d, 0x39, 0x5a, 0x8e, 0xf5, 0xab, 0xe3, 0x02, 0x5b, 0x75, 0xc9, + 0x13, 0xfc, 0x08, 0x97, 0x93, 0x77, 0x47, 0x3e, 0xe8, 0x0f, 0x3f, 0x7e, 0x7d, 0xfd, 0xc8, 0x93, + 0xde, 0xe2, 0xf6, 0x0d, 0x3c, 0x2f, 0xcd, 0xe6, 0xae, 0xb1, 0x6a, 0x90, 0x38, 0x6a, 0x7e, 0x94, + 0x8a, 0x6e, 0x57, 0x5f, 0x9c, 0xcd, 0xb7, 0xa1, 0xc6, 0x5b, 0xb6, 0x5b, 0xf6, 0x7d, 0xbe, 0xf9, + 0x1d, 0x00, 0x00, 0xff, 0xff, 0x47, 0xd9, 0x92, 0x62, 0xdd, 0x02, 0x00, 0x00, } diff --git a/rpc/lspd.proto b/rpc/lspd.proto index 42a7c75..cdb16ba 100644 --- a/rpc/lspd.proto +++ b/rpc/lspd.proto @@ -53,4 +53,6 @@ message OpenChannelRequest { message OpenChannelReply { /// The transaction hash string tx_hash = 1 [ json_name = "tx_hash" ]; + /// The output index + uint32 output_index = 2 [ json_name = "output_index"]; } diff --git a/server.go b/server.go index 8d9d490..c2b6a64 100644 --- a/server.go +++ b/server.go @@ -56,6 +56,7 @@ func (s *server) OpenChannel(ctx context.Context, in *lspdrpc.OpenChannelRequest return nil, err } var txidStr string + var outputIndex uint32 if len(nodeChannels) == 0 && len(pendingChannels) == 0 { response, err := client.OpenChannelSync(clientCtx, &lnrpc.OpenChannelRequest{ LocalFundingAmount: channelAmount, @@ -73,14 +74,14 @@ func (s *server) OpenChannel(ctx context.Context, in *lspdrpc.OpenChannelRequest } 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}, nil + return &lspdrpc.OpenChannelReply{TxHash: txidStr, OutputIndex: outputIndex}, nil }) if err != nil { From a4ab8bf8fa84f2d7bdb6b8baef22d746ab025939 Mon Sep 17 00:00:00 2001 From: Yaacov Akiba Slama Date: Mon, 22 Jul 2019 16:42:23 +0300 Subject: [PATCH 007/214] Add authorization check using token --- go.mod | 1 + sample.env | 4 +++- server.go | 16 +++++++++++++++- 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 8d9b40e..387c15d 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.12 require ( github.com/btcsuite/btcd v0.0.0-20190629003639-c26ffa870fd8 github.com/golang/protobuf v1.3.2 + github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 github.com/lightningnetwork/lnd v0.7.0-beta golang.org/x/net v0.0.0-20190628185345-da137c7871d7 golang.org/x/sync v0.0.0-20190423024810-112230192c58 diff --git a/sample.env b/sample.env index 61c2e90..a992a39 100644 --- a/sample.env +++ b/sample.env @@ -6,4 +6,6 @@ LND_MACAROON_HEX= NODE_NAME= NODE_PUBKEY= -NODE_HOST= \ No newline at end of file +NODE_HOST= + +TOKEN= \ No newline at end of file diff --git a/server.go b/server.go index c2b6a64..175d4e9 100644 --- a/server.go +++ b/server.go @@ -12,11 +12,14 @@ import ( lspdrpc "github.com/breez/lspd/rpc" "github.com/btcsuite/btcd/chaincfg/chainhash" + grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware" "github.com/lightningnetwork/lnd/lnrpc" "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 ( @@ -141,7 +144,18 @@ func main() { defer conn.Close() client = lnrpc.NewLightningClient(conn) - s := grpc.NewServer() + 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) From 254b2c9dfb62a28dd690fdaef4a12ddab00b30d8 Mon Sep 17 00:00:00 2001 From: Yaacov Akiba Slama Date: Wed, 14 Aug 2019 16:24:45 +0300 Subject: [PATCH 008/214] Add lspd grpc documentation --- rpc/lspd.md | 136 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 136 insertions(+) create mode 100644 rpc/lspd.md diff --git a/rpc/lspd.md b/rpc/lspd.md new file mode 100644 index 0000000..2f86135 --- /dev/null +++ b/rpc/lspd.md @@ -0,0 +1,136 @@ +# Protocol Documentation + + +## Table of Contents + +- [lspd.proto](#lspd.proto) + - [ChannelInformationReply](#lspd.ChannelInformationReply) + - [ChannelInformationRequest](#lspd.ChannelInformationRequest) + - [OpenChannelReply](#lspd.OpenChannelReply) + - [OpenChannelRequest](#lspd.OpenChannelRequest) + + + + - [ChannelOpener](#lspd.ChannelOpener) + + +- [Scalar Value Types](#scalar-value-types) + + + + +

Top

+ +## lspd.proto + + + + + +### ChannelInformationReply + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| name | [string](#string) | | The name of of lsp | +| pubkey | [string](#string) | | The identity pubkey of the Lightning node | +| host | [string](#string) | | The network location of the lightning node, e.g. `12.34.56.78:9012` or / `localhost:10011` | +| channel_capacity | [int64](#int64) | | The channel capacity in satoshis | +| target_conf | [int32](#int32) | | The target number of blocks that the funding transaction should be / confirmed by. | +| base_fee_msat | [int64](#int64) | | The base fee charged regardless of the number of milli-satoshis sent. | +| fee_rate | [double](#double) | | The effective fee rate in milli-satoshis. The precision of this value goes / up to 6 decimal places, so 1e-6. | +| time_lock_delta | [uint32](#uint32) | | The required timelock delta for HTLCs forwarded over the channel. | +| min_htlc_msat | [int64](#int64) | | The minimum value in millisatoshi we will require for incoming HTLCs on / the channel. | + + + + + + + + +### ChannelInformationRequest + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| pubkey | [string](#string) | | The identity pubkey of the Lightning node | + + + + + + + + +### OpenChannelReply + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| tx_hash | [string](#string) | | The transaction hash | +| output_index | [uint32](#uint32) | | The output index | + + + + + + + + +### OpenChannelRequest + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| pubkey | [string](#string) | | The identity pubkey of the Lightning node | + + + + + + + + + + + + + + +### ChannelOpener + + +| Method Name | Request Type | Response Type | Description | +| ----------- | ------------ | ------------- | ------------| +| ChannelInformation | [ChannelInformationRequest](#lspd.ChannelInformationRequest) | [ChannelInformationReply](#lspd.ChannelInformationReply) | | +| OpenChannel | [OpenChannelRequest](#lspd.OpenChannelRequest) | [OpenChannelReply](#lspd.OpenChannelReply) | | + + + + + +## Scalar Value Types + +| .proto Type | Notes | C++ Type | Java Type | Python Type | +| ----------- | ----- | -------- | --------- | ----------- | +| double | | double | double | float | +| float | | float | float | float | +| int32 | Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint32 instead. | int32 | int | int | +| int64 | Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint64 instead. | int64 | long | int/long | +| uint32 | Uses variable-length encoding. | uint32 | int | int/long | +| uint64 | Uses variable-length encoding. | uint64 | long | int/long | +| sint32 | Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int32s. | int32 | int | int | +| sint64 | Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int64s. | int64 | long | int/long | +| fixed32 | Always four bytes. More efficient than uint32 if values are often greater than 2^28. | uint32 | int | int | +| fixed64 | Always eight bytes. More efficient than uint64 if values are often greater than 2^56. | uint64 | long | int/long | +| sfixed32 | Always four bytes. | int32 | int | int | +| sfixed64 | Always eight bytes. | int64 | long | int/long | +| bool | | bool | boolean | boolean | +| string | A string must always contain UTF-8 encoded or 7-bit ASCII text. | string | String | str/unicode | +| bytes | May contain any arbitrary sequence of bytes. | string | ByteString | str | + From 3eaae4d273841ebbc050dcf8e7a2632469f59b6a Mon Sep 17 00:00:00 2001 From: Yaacov Akiba Slama Date: Wed, 14 Aug 2019 16:54:08 +0300 Subject: [PATCH 009/214] Create README.md --- README.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..624d07b --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# lspd From 55dc828b629889f023d915989a2255ab53236d2a Mon Sep 17 00:00:00 2001 From: Yaacov Akiba Slama Date: Wed, 14 Aug 2019 17:47:54 +0300 Subject: [PATCH 010/214] Add some documentation --- README.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 624d07b..193f930 100644 --- a/README.md +++ b/README.md @@ -1 +1,11 @@ -# lspd +# lspd simple server +This sample server works with a lnd node +## Installation +1. git clone https://github.com/breez/lspd (or fork) +1. Modify the code in server.go if you use different values than the default when opening channels +1. Compile lspd using `go build .` +1. Define the environment variables as described in sample.env +1. Run lspd +1. Share with breez the TOKEN and the LISTEN_ADDRESS you defined +## Implement your own server +The grpc methods are described in https://github.com/breez/lspd/blob/master/rpc/lspd.md From 35afbb5efacfbddc95f76dc45654c4da3deca8a5 Mon Sep 17 00:00:00 2001 From: kingonly <31890660+kingonly@users.noreply.github.com> Date: Thu, 15 Aug 2019 14:58:32 +0300 Subject: [PATCH 011/214] Update README.md --- README.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 193f930..aeeaeb6 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,14 @@ # lspd simple server -This sample server works with a lnd node +This is a simple example of an lspd server that works with an [lnd](https://github.com/lightningnetwork/lnd) node. +This server exposes LSP services to the [Breez client](https://github.com/breez/breezmobile). + ## Installation 1. git clone https://github.com/breez/lspd (or fork) 1. Modify the code in server.go if you use different values than the default when opening channels 1. Compile lspd using `go build .` 1. Define the environment variables as described in sample.env 1. Run lspd -1. Share with breez the TOKEN and the LISTEN_ADDRESS you defined +1. Share with Breez the TOKEN and the LISTEN_ADDRESS you defined + ## Implement your own server The grpc methods are described in https://github.com/breez/lspd/blob/master/rpc/lspd.md From 64c18c879becb4f4482a4e4f5e6228c918ba4be7 Mon Sep 17 00:00:00 2001 From: kingonly <31890660+kingonly@users.noreply.github.com> Date: Thu, 15 Aug 2019 14:59:12 +0300 Subject: [PATCH 012/214] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index aeeaeb6..3135530 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ # lspd simple server This is a simple example of an lspd server that works with an [lnd](https://github.com/lightningnetwork/lnd) node. + This server exposes LSP services to the [Breez client](https://github.com/breez/breezmobile). ## Installation From ec39a26b5b1f8f9d8a637df2fd50fd9bb127fbb3 Mon Sep 17 00:00:00 2001 From: kingonly <31890660+kingonly@users.noreply.github.com> Date: Thu, 15 Aug 2019 15:01:12 +0300 Subject: [PATCH 013/214] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3135530..34f162b 100644 --- a/README.md +++ b/README.md @@ -12,4 +12,4 @@ This server exposes LSP services to the [Breez client](https://github.com/breez/ 1. Share with Breez the TOKEN and the LISTEN_ADDRESS you defined ## Implement your own server -The grpc methods are described in https://github.com/breez/lspd/blob/master/rpc/lspd.md +You can create your own server by implementing the grpc methods described [here](https://github.com/breez/lspd/blob/master/rpc/lspd.md). From 1e4a9bd96f3987bdbe652b7735e1a2e21a84c99f Mon Sep 17 00:00:00 2001 From: kingonly <31890660+kingonly@users.noreply.github.com> Date: Thu, 15 Aug 2019 15:28:25 +0300 Subject: [PATCH 014/214] Update README.md --- README.md | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 34f162b..bddb67a 100644 --- a/README.md +++ b/README.md @@ -5,11 +5,17 @@ This server exposes LSP services to the [Breez client](https://github.com/breez/ ## Installation 1. git clone https://github.com/breez/lspd (or fork) -1. Modify the code in server.go if you use different values than the default when opening channels -1. Compile lspd using `go build .` -1. Define the environment variables as described in sample.env -1. Run lspd -1. Share with Breez the TOKEN and the LISTEN_ADDRESS you defined +2. Modify the code in server.go if you use different values than the recommeded values when opening channels: + * **ChannelCapacity**: channel capacity is sats, defined in the channelAmount const (recommended: 1000000). + * **TargetConf**: the number of blocks that the funding transaction *should* confirm in, will be used for fee estimation (recommended: 0). + * **MinHtlcMsat**: the channel_reserve value in sats (recommended: 1000000). + * **BaseFeeMsat**: base tx fee in msats (recommended: 1000). + * **FeeRate**: fee rate (recommended: 0.000001). + * **TimeLockDelta**: the minimum number of blocks this node requires to be added to the expiry of HTLCs (recommended: 144). +3. Compile lspd using `go build .` +4. Define the environment variables as described in sample.env: +5. Run lspd +6. Share with Breez the TOKEN and the LISTEN_ADDRESS you defined ## Implement your own server You can create your own server by implementing the grpc methods described [here](https://github.com/breez/lspd/blob/master/rpc/lspd.md). From 858a12820984c28e34b62b98319387f026e2fbd3 Mon Sep 17 00:00:00 2001 From: kingonly <31890660+kingonly@users.noreply.github.com> Date: Thu, 15 Aug 2019 15:31:48 +0300 Subject: [PATCH 015/214] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index bddb67a..0a9c0ba 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # lspd simple server -This is a simple example of an lspd server that works with an [lnd](https://github.com/lightningnetwork/lnd) node. +lspd is a simple deamon that provides [LSP](https://medium.com/breez-technology/introducing-lightning-service-providers-fe9fb1665d5f) interface to [Breez clients](https://github.com/breez/breezmobile). -This server exposes LSP services to the [Breez client](https://github.com/breez/breezmobile). +This is a simple example of an lspd that works with an [lnd](https://github.com/lightningnetwork/lnd) node. ## Installation 1. git clone https://github.com/breez/lspd (or fork) From 566e175acb6e0aa6d6caae7dab1be6e6776e45fc Mon Sep 17 00:00:00 2001 From: kingonly <31890660+kingonly@users.noreply.github.com> Date: Thu, 15 Aug 2019 15:32:25 +0300 Subject: [PATCH 016/214] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0a9c0ba..4711761 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # lspd simple server -lspd is a simple deamon that provides [LSP](https://medium.com/breez-technology/introducing-lightning-service-providers-fe9fb1665d5f) interface to [Breez clients](https://github.com/breez/breezmobile). +lspd is a simple deamon that provides [LSP](https://medium.com/breez-technology/introducing-lightning-service-providers-fe9fb1665d5f) services to [Breez clients](https://github.com/breez/breezmobile). This is a simple example of an lspd that works with an [lnd](https://github.com/lightningnetwork/lnd) node. From afcfa34d9e5fad4dc9acaded30493f591b585682 Mon Sep 17 00:00:00 2001 From: kingonly <31890660+kingonly@users.noreply.github.com> Date: Thu, 15 Aug 2019 15:33:49 +0300 Subject: [PATCH 017/214] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4711761..7b106b9 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ This is a simple example of an lspd that works with an [lnd](https://github.com/ 3. Compile lspd using `go build .` 4. Define the environment variables as described in sample.env: 5. Run lspd -6. Share with Breez the TOKEN and the LISTEN_ADDRESS you defined +6. Share with Breez the TOKEN and the LISTEN_ADDRESS you've defined (send to contact@breez.technology) ## Implement your own server You can create your own server by implementing the grpc methods described [here](https://github.com/breez/lspd/blob/master/rpc/lspd.md). From da159fe6faef3b289f33acb5c80a89ad98112313 Mon Sep 17 00:00:00 2001 From: kingonly <31890660+kingonly@users.noreply.github.com> Date: Thu, 15 Aug 2019 15:39:10 +0300 Subject: [PATCH 018/214] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7b106b9..82cbe9f 100644 --- a/README.md +++ b/README.md @@ -17,5 +17,5 @@ This is a simple example of an lspd that works with an [lnd](https://github.com/ 5. Run lspd 6. Share with Breez the TOKEN and the LISTEN_ADDRESS you've defined (send to contact@breez.technology) -## Implement your own server -You can create your own server by implementing the grpc methods described [here](https://github.com/breez/lspd/blob/master/rpc/lspd.md). +## Implement your own lspd +You can create your own lsdp by implementing the grpc methods described [here](https://github.com/breez/lspd/blob/master/rpc/lspd.md). From dbaeb080026680105125a1596b77e0a0ef8abc22 Mon Sep 17 00:00:00 2001 From: Yaacov Akiba Slama Date: Sun, 18 Aug 2019 11:27:16 +0300 Subject: [PATCH 019/214] Improve the description of the parameters related to forward fees --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 82cbe9f..2db08a4 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ This is a simple example of an lspd that works with an [lnd](https://github.com/ * **TargetConf**: the number of blocks that the funding transaction *should* confirm in, will be used for fee estimation (recommended: 0). * **MinHtlcMsat**: the channel_reserve value in sats (recommended: 1000000). * **BaseFeeMsat**: base tx fee in msats (recommended: 1000). - * **FeeRate**: fee rate (recommended: 0.000001). + * **FeeRate**: fee rate (recommended: 0.000001). The total fee charged is BaseFeeMsat + (amount * FeeRate / 1000000) * **TimeLockDelta**: the minimum number of blocks this node requires to be added to the expiry of HTLCs (recommended: 144). 3. Compile lspd using `go build .` 4. Define the environment variables as described in sample.env: From aafdb5eee9d2ccb630fdb284cc7cf92a85eec567 Mon Sep 17 00:00:00 2001 From: Yaacov Akiba Slama Date: Sun, 18 Aug 2019 11:27:53 +0300 Subject: [PATCH 020/214] Add a way to generate a random token using openssl --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 2db08a4..c14911a 100644 --- a/README.md +++ b/README.md @@ -13,9 +13,10 @@ This is a simple example of an lspd that works with an [lnd](https://github.com/ * **FeeRate**: fee rate (recommended: 0.000001). The total fee charged is BaseFeeMsat + (amount * FeeRate / 1000000) * **TimeLockDelta**: the minimum number of blocks this node requires to be added to the expiry of HTLCs (recommended: 144). 3. Compile lspd using `go build .` -4. Define the environment variables as described in sample.env: -5. Run lspd -6. Share with Breez the TOKEN and the LISTEN_ADDRESS you've defined (send to contact@breez.technology) +4. Create a random token (for instance using the command `openssl rand -base64 48`) +5. Define the environment variables as described in sample.env: +6. Run lspd +7. Share with Breez the TOKEN and the LISTEN_ADDRESS you've defined (send to contact@breez.technology) ## Implement your own lspd You can create your own lsdp by implementing the grpc methods described [here](https://github.com/breez/lspd/blob/master/rpc/lspd.md). From d897761c3cae143e15455022069e0e30bd955a1c Mon Sep 17 00:00:00 2001 From: Yaacov Akiba Slama Date: Sun, 18 Aug 2019 11:32:47 +0300 Subject: [PATCH 021/214] Use constants instead of hard coded values --- server.go | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/server.go b/server.go index 175d4e9..6cf1798 100644 --- a/server.go +++ b/server.go @@ -24,6 +24,11 @@ import ( const ( channelAmount = 1000000 + targetConf = 1 + minHtlcMsat = 600 + baseFeeMsat = 1000 + feeRate = 0.000001 + timeLockDelta = 144 ) type server struct{} @@ -39,11 +44,11 @@ func (s *server) ChannelInformation(ctx context.Context, in *lspdrpc.ChannelInfo Pubkey: os.Getenv("NODE_PUBKEY"), Host: os.Getenv("NODE_HOST"), ChannelCapacity: channelAmount, - TargetConf: 1, - MinHtlcMsat: 600, - BaseFeeMsat: 1000, - FeeRate: 0.000001, - TimeLockDelta: 144, + TargetConf: targetConf, + MinHtlcMsat: minHtlcMsat, + BaseFeeMsat: baseFeeMsat, + FeeRate: feeRate, + TimeLockDelta: timeLockDelta, }, nil } @@ -65,8 +70,8 @@ func (s *server) OpenChannel(ctx context.Context, in *lspdrpc.OpenChannelRequest LocalFundingAmount: channelAmount, NodePubkeyString: in.Pubkey, PushSat: 0, - TargetConf: 1, - MinHtlcMsat: 600, + TargetConf: targetConf, + MinHtlcMsat: minHtlcMsat, Private: true, }) log.Printf("Response from OpenChannel: %#v (TX: %v)", response, hex.EncodeToString(response.GetFundingTxidBytes())) From b08cc2b438fe4afb209af5cafd41085807fed933 Mon Sep 17 00:00:00 2001 From: Yaacov Akiba Slama Date: Sun, 18 Aug 2019 11:37:56 +0300 Subject: [PATCH 022/214] Add instructions for customizing the channel reserve --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index c14911a..5fb1453 100644 --- a/README.md +++ b/README.md @@ -20,3 +20,7 @@ This is a simple example of an lspd that works with an [lnd](https://github.com/ ## Implement your own lspd You can create your own lsdp by implementing the grpc methods described [here](https://github.com/breez/lspd/blob/master/rpc/lspd.md). + +## Use a smaller channel reserve +You can apply the PR from https://github.com/lightningnetwork/lnd/pull/270 to be able to create channels with a channel reserve smaller than 1% of the channel capacity. +Then add the field `RemoteChanReserveSat` in the `lnrpc.OpenChannelRequest` struct when opening a channel. From 1d6aa3a328c3bf65f8f9499088fe0fecf51a85e9 Mon Sep 17 00:00:00 2001 From: Yaacov Akiba Slama Date: Mon, 26 Aug 2019 19:19:57 +0300 Subject: [PATCH 023/214] Fix the PR link to customize the channel reserve --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5fb1453..886fa86 100644 --- a/README.md +++ b/README.md @@ -22,5 +22,5 @@ This is a simple example of an lspd that works with an [lnd](https://github.com/ You can create your own lsdp by implementing the grpc methods described [here](https://github.com/breez/lspd/blob/master/rpc/lspd.md). ## Use a smaller channel reserve -You can apply the PR from https://github.com/lightningnetwork/lnd/pull/270 to be able to create channels with a channel reserve smaller than 1% of the channel capacity. +You can apply the PR from https://github.com/lightningnetwork/lnd/pull/2708 to be able to create channels with a channel reserve smaller than 1% of the channel capacity. Then add the field `RemoteChanReserveSat` in the `lnrpc.OpenChannelRequest` struct when opening a channel. From 795625ca3fff8b9e778f44f654598f9132fb7a9a Mon Sep 17 00:00:00 2001 From: Yaacov Akiba Slama Date: Tue, 27 Aug 2019 18:10:45 +0300 Subject: [PATCH 024/214] Add optional support for Let's Encrypt certificate --- README.md | 2 +- go.mod | 1 + sample.env | 3 +++ server.go | 23 ++++++++++++++++++++--- 4 files changed, 25 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 886fa86..bc66a66 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ This is a simple example of an lspd that works with an [lnd](https://github.com/ * **TimeLockDelta**: the minimum number of blocks this node requires to be added to the expiry of HTLCs (recommended: 144). 3. Compile lspd using `go build .` 4. Create a random token (for instance using the command `openssl rand -base64 48`) -5. Define the environment variables as described in sample.env: +5. Define the environment variables as described in sample.env. If `CERTMAGIC_DOMAIN` is defined, certificate for this domain is automatically obtained and renewed from Let's Encrypt. In this case, the port needs to be 443. If `CERTMAGIC_DOMAIN` is not defined, lspd needs to run behind a reverse proxy like treafik or nginx. 6. Run lspd 7. Share with Breez the TOKEN and the LISTEN_ADDRESS you've defined (send to contact@breez.technology) diff --git a/go.mod b/go.mod index 387c15d..f00a7f8 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/golang/protobuf v1.3.2 github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 github.com/lightningnetwork/lnd v0.7.0-beta + github.com/mholt/certmagic v0.6.2 golang.org/x/net v0.0.0-20190628185345-da137c7871d7 golang.org/x/sync v0.0.0-20190423024810-112230192c58 google.golang.org/grpc v1.22.0 diff --git a/sample.env b/sample.env index a992a39..7b37a19 100644 --- a/sample.env +++ b/sample.env @@ -1,4 +1,7 @@ LISTEN_ADDRESS= +### If you define a domain here, the server will use certmagic to obtain +### a certificate from Let's Encrypt +#CERTMAGIC_DOMAIN= LND_ADDRESS= LND_CERT= #replace each eol by \\n diff --git a/server.go b/server.go index 6cf1798..9890b80 100644 --- a/server.go +++ b/server.go @@ -2,6 +2,7 @@ package main import ( "context" + "crypto/tls" "crypto/x509" "encoding/hex" "log" @@ -14,6 +15,7 @@ import ( "github.com/btcsuite/btcd/chaincfg/chainhash" grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware" "github.com/lightningnetwork/lnd/lnrpc" + "github.com/mholt/certmagic" "golang.org/x/sync/singleflight" "google.golang.org/grpc" "google.golang.org/grpc/codes" @@ -129,9 +131,24 @@ func getPendingNodeChannels(nodeID string) ([]*lnrpc.PendingChannelsResponse_Pen } func main() { - lis, err := net.Listen("tcp", os.Getenv("LISTEN_ADDRESS")) - if err != nil { - log.Fatalf("Failed to listen: %v", err) + 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 From 8dbb2f54ca62d36b2e028d854bf00921ff19d374 Mon Sep 17 00:00:00 2001 From: Yaacov Akiba Slama Date: Mon, 4 Nov 2019 16:50:53 +0200 Subject: [PATCH 025/214] Update lnd dependency --- go.mod | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index f00a7f8..5ccf400 100644 --- a/go.mod +++ b/go.mod @@ -3,10 +3,10 @@ module github.com/breez/lspd go 1.12 require ( - github.com/btcsuite/btcd v0.0.0-20190629003639-c26ffa870fd8 + github.com/btcsuite/btcd v0.20.0-beta github.com/golang/protobuf v1.3.2 github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 - github.com/lightningnetwork/lnd v0.7.0-beta + github.com/lightningnetwork/lnd v0.8.0-beta github.com/mholt/certmagic v0.6.2 golang.org/x/net v0.0.0-20190628185345-da137c7871d7 golang.org/x/sync v0.0.0-20190423024810-112230192c58 From a5db418f43ea05020af545ec54113a6e35ce682a Mon Sep 17 00:00:00 2001 From: Yaacov Akiba Slama Date: Sun, 2 Aug 2020 14:45:47 +0300 Subject: [PATCH 026/214] Add RegisterPayment to lspd --- go.mod | 7 +- rpc/lspd.pb.go | 381 +++++++++++++++++++++++++++++++++++++++---------- rpc/lspd.proto | 18 +++ sample.env | 3 +- server.go | 72 ++++++++-- 5 files changed, 388 insertions(+), 93 deletions(-) diff --git a/go.mod b/go.mod index 5ccf400..699447d 100644 --- a/go.mod +++ b/go.mod @@ -1,14 +1,13 @@ module github.com/breez/lspd -go 1.12 +go 1.14 require ( github.com/btcsuite/btcd v0.20.0-beta - github.com/golang/protobuf v1.3.2 + github.com/golang/protobuf v1.4.2 github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 github.com/lightningnetwork/lnd v0.8.0-beta github.com/mholt/certmagic v0.6.2 - golang.org/x/net v0.0.0-20190628185345-da137c7871d7 golang.org/x/sync v0.0.0-20190423024810-112230192c58 - google.golang.org/grpc v1.22.0 + google.golang.org/grpc v1.31.0 ) diff --git a/rpc/lspd.pb.go b/rpc/lspd.pb.go index 69793b9..8b351ea 100644 --- a/rpc/lspd.pb.go +++ b/rpc/lspd.pb.go @@ -3,13 +3,14 @@ package lspd -import proto "github.com/golang/protobuf/proto" -import fmt "fmt" -import math "math" - import ( - context "golang.org/x/net/context" + context "context" + fmt "fmt" + proto "github.com/golang/protobuf/proto" grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + math "math" ) // Reference imports to suppress errors if they are not otherwise used. @@ -21,10 +22,10 @@ var _ = math.Inf // is compatible with the proto package it is being compiled against. // A compilation error at this line likely means your copy of the // proto package needs to be updated. -const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package +const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package type ChannelInformationRequest struct { - // / The identity pubkey of the Lightning node + /// The identity pubkey of the Lightning node Pubkey string `protobuf:"bytes,1,opt,name=pubkey,proto3" json:"pubkey,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` @@ -35,16 +36,17 @@ func (m *ChannelInformationRequest) Reset() { *m = ChannelInformationReq func (m *ChannelInformationRequest) String() string { return proto.CompactTextString(m) } func (*ChannelInformationRequest) ProtoMessage() {} func (*ChannelInformationRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_lspd_f6548acf9d60fb70, []int{0} + return fileDescriptor_c69a0f5a734bca26, []int{0} } + func (m *ChannelInformationRequest) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_ChannelInformationRequest.Unmarshal(m, b) } func (m *ChannelInformationRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { return xxx_messageInfo_ChannelInformationRequest.Marshal(b, m, deterministic) } -func (dst *ChannelInformationRequest) XXX_Merge(src proto.Message) { - xxx_messageInfo_ChannelInformationRequest.Merge(dst, src) +func (m *ChannelInformationRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_ChannelInformationRequest.Merge(m, src) } func (m *ChannelInformationRequest) XXX_Size() int { return xxx_messageInfo_ChannelInformationRequest.Size(m) @@ -63,47 +65,51 @@ func (m *ChannelInformationRequest) GetPubkey() string { } type ChannelInformationReply struct { - // / The name of of lsp + /// The name of of lsp Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` - // / The identity pubkey of the Lightning node + /// The identity pubkey of the Lightning node Pubkey string `protobuf:"bytes,2,opt,name=pubkey,proto3" json:"pubkey,omitempty"` - // / The network location of the lightning node, e.g. `12.34.56.78:9012` or - // / `localhost:10011` + /// The network location of the lightning node, e.g. `12.34.56.78:9012` or + /// `localhost:10011` Host string `protobuf:"bytes,3,opt,name=host,proto3" json:"host,omitempty"` - // / The channel capacity in satoshis + /// The channel capacity in satoshis ChannelCapacity int64 `protobuf:"varint,4,opt,name=channel_capacity,proto3" json:"channel_capacity,omitempty"` - // / The target number of blocks that the funding transaction should be - // / confirmed by. + /// The target number of blocks that the funding transaction should be + /// confirmed by. TargetConf int32 `protobuf:"varint,5,opt,name=target_conf,proto3" json:"target_conf,omitempty"` - // / The base fee charged regardless of the number of milli-satoshis sent. + /// The base fee charged regardless of the number of milli-satoshis sent. BaseFeeMsat int64 `protobuf:"varint,6,opt,name=base_fee_msat,proto3" json:"base_fee_msat,omitempty"` - // / The effective fee rate in milli-satoshis. The precision of this value goes - // / up to 6 decimal places, so 1e-6. + /// The effective fee rate in milli-satoshis. The precision of this value goes + /// up to 6 decimal places, so 1e-6. FeeRate float64 `protobuf:"fixed64,7,opt,name=fee_rate,proto3" json:"fee_rate,omitempty"` - // / The required timelock delta for HTLCs forwarded over the channel. + /// The required timelock delta for HTLCs forwarded over the channel. TimeLockDelta uint32 `protobuf:"varint,8,opt,name=time_lock_delta,proto3" json:"time_lock_delta,omitempty"` - // / The minimum value in millisatoshi we will require for incoming HTLCs on - // / the channel. - MinHtlcMsat int64 `protobuf:"varint,9,opt,name=min_htlc_msat,proto3" json:"min_htlc_msat,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + /// The minimum value in millisatoshi we will require for incoming HTLCs on + /// the channel. + MinHtlcMsat int64 `protobuf:"varint,9,opt,name=min_htlc_msat,proto3" json:"min_htlc_msat,omitempty"` + ChannelFeeStartAmount int64 `protobuf:"varint,10,opt,name=channel_fee_start_amount,json=channelFeeStartAmount,proto3" json:"channel_fee_start_amount,omitempty"` + ChannelFeeRate float32 `protobuf:"fixed32,11,opt,name=channel_fee_rate,json=channelFeeRate,proto3" json:"channel_fee_rate,omitempty"` + LspPubkey []byte `protobuf:"bytes,12,opt,name=lsp_pubkey,json=lspPubkey,proto3" json:"lsp_pubkey,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } func (m *ChannelInformationReply) Reset() { *m = ChannelInformationReply{} } func (m *ChannelInformationReply) String() string { return proto.CompactTextString(m) } func (*ChannelInformationReply) ProtoMessage() {} func (*ChannelInformationReply) Descriptor() ([]byte, []int) { - return fileDescriptor_lspd_f6548acf9d60fb70, []int{1} + return fileDescriptor_c69a0f5a734bca26, []int{1} } + func (m *ChannelInformationReply) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_ChannelInformationReply.Unmarshal(m, b) } func (m *ChannelInformationReply) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { return xxx_messageInfo_ChannelInformationReply.Marshal(b, m, deterministic) } -func (dst *ChannelInformationReply) XXX_Merge(src proto.Message) { - xxx_messageInfo_ChannelInformationReply.Merge(dst, src) +func (m *ChannelInformationReply) XXX_Merge(src proto.Message) { + xxx_messageInfo_ChannelInformationReply.Merge(m, src) } func (m *ChannelInformationReply) XXX_Size() int { return xxx_messageInfo_ChannelInformationReply.Size(m) @@ -177,8 +183,29 @@ func (m *ChannelInformationReply) GetMinHtlcMsat() int64 { return 0 } +func (m *ChannelInformationReply) GetChannelFeeStartAmount() int64 { + if m != nil { + return m.ChannelFeeStartAmount + } + return 0 +} + +func (m *ChannelInformationReply) GetChannelFeeRate() float32 { + if m != nil { + return m.ChannelFeeRate + } + return 0 +} + +func (m *ChannelInformationReply) GetLspPubkey() []byte { + if m != nil { + return m.LspPubkey + } + return nil +} + type OpenChannelRequest struct { - // / The identity pubkey of the Lightning node + /// The identity pubkey of the Lightning node Pubkey string `protobuf:"bytes,1,opt,name=pubkey,proto3" json:"pubkey,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` @@ -189,16 +216,17 @@ func (m *OpenChannelRequest) Reset() { *m = OpenChannelRequest{} } func (m *OpenChannelRequest) String() string { return proto.CompactTextString(m) } func (*OpenChannelRequest) ProtoMessage() {} func (*OpenChannelRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_lspd_f6548acf9d60fb70, []int{2} + return fileDescriptor_c69a0f5a734bca26, []int{2} } + func (m *OpenChannelRequest) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_OpenChannelRequest.Unmarshal(m, b) } func (m *OpenChannelRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { return xxx_messageInfo_OpenChannelRequest.Marshal(b, m, deterministic) } -func (dst *OpenChannelRequest) XXX_Merge(src proto.Message) { - xxx_messageInfo_OpenChannelRequest.Merge(dst, src) +func (m *OpenChannelRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_OpenChannelRequest.Merge(m, src) } func (m *OpenChannelRequest) XXX_Size() int { return xxx_messageInfo_OpenChannelRequest.Size(m) @@ -217,9 +245,9 @@ func (m *OpenChannelRequest) GetPubkey() string { } type OpenChannelReply struct { - // / The transaction hash + /// The transaction hash TxHash string `protobuf:"bytes,1,opt,name=tx_hash,proto3" json:"tx_hash,omitempty"` - // / The output index + /// The output index OutputIndex uint32 `protobuf:"varint,2,opt,name=output_index,proto3" json:"output_index,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` @@ -230,16 +258,17 @@ func (m *OpenChannelReply) Reset() { *m = OpenChannelReply{} } func (m *OpenChannelReply) String() string { return proto.CompactTextString(m) } func (*OpenChannelReply) ProtoMessage() {} func (*OpenChannelReply) Descriptor() ([]byte, []int) { - return fileDescriptor_lspd_f6548acf9d60fb70, []int{3} + return fileDescriptor_c69a0f5a734bca26, []int{3} } + func (m *OpenChannelReply) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_OpenChannelReply.Unmarshal(m, b) } func (m *OpenChannelReply) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { return xxx_messageInfo_OpenChannelReply.Marshal(b, m, deterministic) } -func (dst *OpenChannelReply) XXX_Merge(src proto.Message) { - xxx_messageInfo_OpenChannelReply.Merge(dst, src) +func (m *OpenChannelReply) XXX_Merge(src proto.Message) { + xxx_messageInfo_OpenChannelReply.Merge(m, src) } func (m *OpenChannelReply) XXX_Size() int { return xxx_messageInfo_OpenChannelReply.Size(m) @@ -264,20 +293,209 @@ func (m *OpenChannelReply) GetOutputIndex() uint32 { return 0 } +type RegisterPaymentRequest struct { + Blob []byte `protobuf:"bytes,3,opt,name=blob,proto3" json:"blob,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *RegisterPaymentRequest) Reset() { *m = RegisterPaymentRequest{} } +func (m *RegisterPaymentRequest) String() string { return proto.CompactTextString(m) } +func (*RegisterPaymentRequest) ProtoMessage() {} +func (*RegisterPaymentRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_c69a0f5a734bca26, []int{4} +} + +func (m *RegisterPaymentRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_RegisterPaymentRequest.Unmarshal(m, b) +} +func (m *RegisterPaymentRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_RegisterPaymentRequest.Marshal(b, m, deterministic) +} +func (m *RegisterPaymentRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_RegisterPaymentRequest.Merge(m, src) +} +func (m *RegisterPaymentRequest) XXX_Size() int { + return xxx_messageInfo_RegisterPaymentRequest.Size(m) +} +func (m *RegisterPaymentRequest) XXX_DiscardUnknown() { + xxx_messageInfo_RegisterPaymentRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_RegisterPaymentRequest proto.InternalMessageInfo + +func (m *RegisterPaymentRequest) GetBlob() []byte { + if m != nil { + return m.Blob + } + return nil +} + +type RegisterPaymentReply struct { + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *RegisterPaymentReply) Reset() { *m = RegisterPaymentReply{} } +func (m *RegisterPaymentReply) String() string { return proto.CompactTextString(m) } +func (*RegisterPaymentReply) ProtoMessage() {} +func (*RegisterPaymentReply) Descriptor() ([]byte, []int) { + return fileDescriptor_c69a0f5a734bca26, []int{5} +} + +func (m *RegisterPaymentReply) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_RegisterPaymentReply.Unmarshal(m, b) +} +func (m *RegisterPaymentReply) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_RegisterPaymentReply.Marshal(b, m, deterministic) +} +func (m *RegisterPaymentReply) XXX_Merge(src proto.Message) { + xxx_messageInfo_RegisterPaymentReply.Merge(m, src) +} +func (m *RegisterPaymentReply) XXX_Size() int { + return xxx_messageInfo_RegisterPaymentReply.Size(m) +} +func (m *RegisterPaymentReply) XXX_DiscardUnknown() { + xxx_messageInfo_RegisterPaymentReply.DiscardUnknown(m) +} + +var xxx_messageInfo_RegisterPaymentReply proto.InternalMessageInfo + +type PaymentInformation struct { + PaymentHash []byte `protobuf:"bytes,1,opt,name=payment_hash,json=paymentHash,proto3" json:"payment_hash,omitempty"` + PaymentSecret []byte `protobuf:"bytes,2,opt,name=payment_secret,json=paymentSecret,proto3" json:"payment_secret,omitempty"` + Destination []byte `protobuf:"bytes,3,opt,name=destination,proto3" json:"destination,omitempty"` + IncomingAmountMsat int64 `protobuf:"varint,4,opt,name=incoming_amount_msat,json=incomingAmountMsat,proto3" json:"incoming_amount_msat,omitempty"` + OutgoingAmountMsat int64 `protobuf:"varint,5,opt,name=outgoing_amount_msat,json=outgoingAmountMsat,proto3" json:"outgoing_amount_msat,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *PaymentInformation) Reset() { *m = PaymentInformation{} } +func (m *PaymentInformation) String() string { return proto.CompactTextString(m) } +func (*PaymentInformation) ProtoMessage() {} +func (*PaymentInformation) Descriptor() ([]byte, []int) { + return fileDescriptor_c69a0f5a734bca26, []int{6} +} + +func (m *PaymentInformation) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_PaymentInformation.Unmarshal(m, b) +} +func (m *PaymentInformation) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_PaymentInformation.Marshal(b, m, deterministic) +} +func (m *PaymentInformation) XXX_Merge(src proto.Message) { + xxx_messageInfo_PaymentInformation.Merge(m, src) +} +func (m *PaymentInformation) XXX_Size() int { + return xxx_messageInfo_PaymentInformation.Size(m) +} +func (m *PaymentInformation) XXX_DiscardUnknown() { + xxx_messageInfo_PaymentInformation.DiscardUnknown(m) +} + +var xxx_messageInfo_PaymentInformation proto.InternalMessageInfo + +func (m *PaymentInformation) GetPaymentHash() []byte { + if m != nil { + return m.PaymentHash + } + return nil +} + +func (m *PaymentInformation) GetPaymentSecret() []byte { + if m != nil { + return m.PaymentSecret + } + return nil +} + +func (m *PaymentInformation) GetDestination() []byte { + if m != nil { + return m.Destination + } + return nil +} + +func (m *PaymentInformation) GetIncomingAmountMsat() int64 { + if m != nil { + return m.IncomingAmountMsat + } + return 0 +} + +func (m *PaymentInformation) GetOutgoingAmountMsat() int64 { + if m != nil { + return m.OutgoingAmountMsat + } + return 0 +} + func init() { proto.RegisterType((*ChannelInformationRequest)(nil), "lspd.ChannelInformationRequest") proto.RegisterType((*ChannelInformationReply)(nil), "lspd.ChannelInformationReply") proto.RegisterType((*OpenChannelRequest)(nil), "lspd.OpenChannelRequest") proto.RegisterType((*OpenChannelReply)(nil), "lspd.OpenChannelReply") + proto.RegisterType((*RegisterPaymentRequest)(nil), "lspd.RegisterPaymentRequest") + proto.RegisterType((*RegisterPaymentReply)(nil), "lspd.RegisterPaymentReply") + proto.RegisterType((*PaymentInformation)(nil), "lspd.PaymentInformation") +} + +func init() { + proto.RegisterFile("lspd.proto", fileDescriptor_c69a0f5a734bca26) +} + +var fileDescriptor_c69a0f5a734bca26 = []byte{ + // 586 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x84, 0x54, 0xdd, 0x6e, 0xd3, 0x4c, + 0x10, 0xfd, 0xdc, 0xa6, 0x3f, 0x99, 0x38, 0x6d, 0x35, 0xca, 0x17, 0x4c, 0x44, 0x85, 0x09, 0x20, + 0x59, 0xa8, 0xaa, 0x2a, 0x7a, 0xc1, 0x75, 0x8b, 0x84, 0x40, 0xa2, 0x22, 0xda, 0x4a, 0xdc, 0x5a, + 0x1b, 0x67, 0x9a, 0x58, 0xb5, 0x77, 0x8d, 0x77, 0x83, 0x9a, 0x67, 0xe3, 0x75, 0x78, 0x09, 0xee, + 0xd0, 0xfe, 0xa4, 0x71, 0x9a, 0x56, 0xdc, 0xcd, 0x9e, 0x39, 0x33, 0x7b, 0x3c, 0x67, 0xd6, 0x00, + 0x85, 0xaa, 0x26, 0xa7, 0x55, 0x2d, 0xb5, 0xc4, 0x96, 0x89, 0x87, 0xe7, 0xf0, 0xfc, 0xe3, 0x8c, + 0x0b, 0x41, 0xc5, 0x17, 0x71, 0x23, 0xeb, 0x92, 0xeb, 0x5c, 0x0a, 0x46, 0x3f, 0xe6, 0xa4, 0x34, + 0xf6, 0x61, 0xb7, 0x9a, 0x8f, 0x6f, 0x69, 0x11, 0x05, 0x71, 0x90, 0xb4, 0x99, 0x3f, 0x0d, 0x7f, + 0x6d, 0xc3, 0xb3, 0xc7, 0xaa, 0xaa, 0x62, 0x81, 0x08, 0x2d, 0xc1, 0x4b, 0xf2, 0x15, 0x36, 0x6e, + 0xf4, 0xd9, 0x6a, 0xf6, 0x31, 0xdc, 0x99, 0x54, 0x3a, 0xda, 0x76, 0x5c, 0x13, 0xe3, 0x3b, 0x38, + 0xca, 0x5c, 0xeb, 0x34, 0xe3, 0x15, 0xcf, 0x72, 0xbd, 0x88, 0x5a, 0x71, 0x90, 0x6c, 0xb3, 0x0d, + 0x1c, 0x63, 0xe8, 0x68, 0x5e, 0x4f, 0x49, 0xa7, 0x99, 0x14, 0x37, 0xd1, 0x4e, 0x1c, 0x24, 0x3b, + 0xac, 0x09, 0xe1, 0x1b, 0xe8, 0x8e, 0xb9, 0xa2, 0xf4, 0x86, 0x28, 0x2d, 0x15, 0xd7, 0xd1, 0xae, + 0x6d, 0xb5, 0x0e, 0xe2, 0x00, 0xf6, 0x4d, 0x5c, 0x73, 0x4d, 0xd1, 0x5e, 0x1c, 0x24, 0x01, 0xbb, + 0x3f, 0x63, 0x02, 0x87, 0x3a, 0x2f, 0x29, 0x2d, 0x64, 0x76, 0x9b, 0x4e, 0xa8, 0xd0, 0x3c, 0xda, + 0x8f, 0x83, 0xa4, 0xcb, 0x1e, 0xc2, 0xe6, 0xae, 0x32, 0x17, 0xe9, 0x4c, 0x17, 0x99, 0xbb, 0xab, + 0xed, 0xee, 0x5a, 0x03, 0xf1, 0x03, 0x44, 0xcb, 0xef, 0x30, 0x77, 0x28, 0xcd, 0x6b, 0x9d, 0xf2, + 0x52, 0xce, 0x85, 0x8e, 0xc0, 0x16, 0xfc, 0xef, 0xf3, 0x9f, 0x88, 0xae, 0x4d, 0xf6, 0xc2, 0x26, + 0x31, 0x59, 0x0d, 0xe6, 0x5e, 0x6c, 0x27, 0x0e, 0x92, 0x2d, 0x76, 0xb0, 0x2a, 0x60, 0x46, 0xf2, + 0xb1, 0xf5, 0x39, 0xf5, 0x23, 0x0f, 0xe3, 0x20, 0x09, 0x59, 0xbb, 0x50, 0xd5, 0xc8, 0xb9, 0x77, + 0x02, 0xf8, 0xad, 0x22, 0xe1, 0x0d, 0xfc, 0x97, 0xd7, 0x23, 0x38, 0x5a, 0x63, 0x1b, 0x8f, 0x23, + 0xd8, 0xd3, 0x77, 0xe9, 0x8c, 0xab, 0x99, 0x27, 0x2f, 0x8f, 0x38, 0x84, 0x50, 0xce, 0x75, 0x35, + 0xd7, 0x69, 0x2e, 0x26, 0x74, 0x67, 0xfd, 0xee, 0xb2, 0x35, 0x6c, 0x78, 0x02, 0x7d, 0x46, 0xd3, + 0x5c, 0x69, 0xaa, 0x47, 0x7c, 0x51, 0x92, 0xd0, 0x4b, 0x0d, 0x08, 0xad, 0x71, 0x21, 0xc7, 0x76, + 0x1f, 0x42, 0x66, 0xe3, 0x61, 0x1f, 0x7a, 0x1b, 0xec, 0xaa, 0x58, 0x0c, 0x7f, 0x07, 0x80, 0x1e, + 0x68, 0xec, 0x20, 0xbe, 0x82, 0xb0, 0x72, 0xe8, 0x4a, 0x5f, 0xc8, 0x3a, 0x1e, 0xfb, 0x6c, 0x34, + 0xbe, 0x85, 0x83, 0x25, 0x45, 0x51, 0x56, 0x93, 0xb6, 0x2a, 0x43, 0xd6, 0xf5, 0xe8, 0xb5, 0x05, + 0xcd, 0x72, 0x4d, 0x48, 0xe9, 0x5c, 0xd8, 0xc6, 0x5e, 0x53, 0x13, 0xc2, 0x33, 0xe8, 0xe5, 0x22, + 0x93, 0x65, 0x2e, 0xa6, 0xde, 0x41, 0xe7, 0xbb, 0x5b, 0x57, 0x5c, 0xe6, 0x9c, 0x7f, 0x57, 0xc6, + 0xfc, 0x33, 0xe8, 0xc9, 0xb9, 0x9e, 0xca, 0x87, 0x15, 0x3b, 0xae, 0x62, 0x99, 0x5b, 0x55, 0xbc, + 0xff, 0x13, 0x40, 0xd7, 0xcf, 0xde, 0xd8, 0x40, 0x35, 0x7e, 0x07, 0xdc, 0x7c, 0x7b, 0xf8, 0xf2, + 0xd4, 0x3e, 0xed, 0x27, 0xdf, 0xf2, 0xe0, 0xf8, 0x69, 0x82, 0x19, 0xe7, 0x7f, 0x78, 0x01, 0x9d, + 0x86, 0xd1, 0x18, 0x39, 0xfe, 0xe6, 0xa6, 0x0c, 0xfa, 0x8f, 0x64, 0x5c, 0x8b, 0x2b, 0x38, 0x7c, + 0xe0, 0x15, 0xbe, 0x70, 0xe4, 0xc7, 0x0d, 0x1f, 0x0c, 0x9e, 0xc8, 0xda, 0x76, 0x97, 0xaf, 0xa1, + 0x97, 0xcb, 0xd3, 0x69, 0x5d, 0x65, 0x8e, 0xa6, 0xa8, 0xfe, 0x99, 0x67, 0x74, 0xd9, 0xfe, 0xaa, + 0xaa, 0xc9, 0xc8, 0xfc, 0xc4, 0x46, 0xc1, 0x78, 0xd7, 0xfe, 0xcd, 0xce, 0xff, 0x06, 0x00, 0x00, + 0xff, 0xff, 0xd7, 0x15, 0xf2, 0x09, 0xdb, 0x04, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. var _ context.Context -var _ grpc.ClientConn +var _ grpc.ClientConnInterface // This is a compile-time assertion to ensure that this generated file // is compatible with the grpc package it is being compiled against. -const _ = grpc.SupportPackageIsVersion4 +const _ = grpc.SupportPackageIsVersion6 // ChannelOpenerClient is the client API for ChannelOpener service. // @@ -285,13 +503,14 @@ const _ = grpc.SupportPackageIsVersion4 type ChannelOpenerClient interface { ChannelInformation(ctx context.Context, in *ChannelInformationRequest, opts ...grpc.CallOption) (*ChannelInformationReply, error) OpenChannel(ctx context.Context, in *OpenChannelRequest, opts ...grpc.CallOption) (*OpenChannelReply, error) + RegisterPayment(ctx context.Context, in *RegisterPaymentRequest, opts ...grpc.CallOption) (*RegisterPaymentReply, error) } type channelOpenerClient struct { - cc *grpc.ClientConn + cc grpc.ClientConnInterface } -func NewChannelOpenerClient(cc *grpc.ClientConn) ChannelOpenerClient { +func NewChannelOpenerClient(cc grpc.ClientConnInterface) ChannelOpenerClient { return &channelOpenerClient{cc} } @@ -313,10 +532,34 @@ func (c *channelOpenerClient) OpenChannel(ctx context.Context, in *OpenChannelRe return out, nil } +func (c *channelOpenerClient) RegisterPayment(ctx context.Context, in *RegisterPaymentRequest, opts ...grpc.CallOption) (*RegisterPaymentReply, error) { + out := new(RegisterPaymentReply) + err := c.cc.Invoke(ctx, "/lspd.ChannelOpener/RegisterPayment", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // ChannelOpenerServer is the server API for ChannelOpener service. type ChannelOpenerServer interface { ChannelInformation(context.Context, *ChannelInformationRequest) (*ChannelInformationReply, error) OpenChannel(context.Context, *OpenChannelRequest) (*OpenChannelReply, error) + RegisterPayment(context.Context, *RegisterPaymentRequest) (*RegisterPaymentReply, error) +} + +// UnimplementedChannelOpenerServer can be embedded to have forward compatible implementations. +type UnimplementedChannelOpenerServer struct { +} + +func (*UnimplementedChannelOpenerServer) ChannelInformation(ctx context.Context, req *ChannelInformationRequest) (*ChannelInformationReply, error) { + return nil, status.Errorf(codes.Unimplemented, "method ChannelInformation not implemented") +} +func (*UnimplementedChannelOpenerServer) OpenChannel(ctx context.Context, req *OpenChannelRequest) (*OpenChannelReply, error) { + return nil, status.Errorf(codes.Unimplemented, "method OpenChannel not implemented") +} +func (*UnimplementedChannelOpenerServer) RegisterPayment(ctx context.Context, req *RegisterPaymentRequest) (*RegisterPaymentReply, error) { + return nil, status.Errorf(codes.Unimplemented, "method RegisterPayment not implemented") } func RegisterChannelOpenerServer(s *grpc.Server, srv ChannelOpenerServer) { @@ -359,6 +602,24 @@ func _ChannelOpener_OpenChannel_Handler(srv interface{}, ctx context.Context, de return interceptor(ctx, in, info, handler) } +func _ChannelOpener_RegisterPayment_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(RegisterPaymentRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ChannelOpenerServer).RegisterPayment(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/lspd.ChannelOpener/RegisterPayment", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ChannelOpenerServer).RegisterPayment(ctx, req.(*RegisterPaymentRequest)) + } + return interceptor(ctx, in, info, handler) +} + var _ChannelOpener_serviceDesc = grpc.ServiceDesc{ ServiceName: "lspd.ChannelOpener", HandlerType: (*ChannelOpenerServer)(nil), @@ -371,37 +632,11 @@ var _ChannelOpener_serviceDesc = grpc.ServiceDesc{ MethodName: "OpenChannel", Handler: _ChannelOpener_OpenChannel_Handler, }, + { + MethodName: "RegisterPayment", + Handler: _ChannelOpener_RegisterPayment_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "lspd.proto", } - -func init() { proto.RegisterFile("lspd.proto", fileDescriptor_lspd_f6548acf9d60fb70) } - -var fileDescriptor_lspd_f6548acf9d60fb70 = []byte{ - // 381 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x84, 0x52, 0xc1, 0x6e, 0xd4, 0x30, - 0x10, 0xc5, 0xed, 0x76, 0xdb, 0x9d, 0x12, 0x51, 0x8d, 0x50, 0x31, 0x2b, 0x21, 0xa2, 0xc0, 0x21, - 0x42, 0x68, 0x0f, 0xf4, 0x0b, 0x28, 0x27, 0x24, 0x24, 0x56, 0x39, 0x70, 0xb5, 0xbc, 0xce, 0x6c, - 0x13, 0x35, 0xb1, 0x4d, 0xec, 0xa0, 0xe6, 0x97, 0xf8, 0x03, 0xfe, 0x0e, 0x39, 0x09, 0x28, 0x61, - 0xbb, 0xe2, 0xf6, 0xe6, 0xe5, 0xcd, 0x9b, 0xe4, 0xe5, 0x01, 0x54, 0xce, 0xe6, 0x1b, 0xdb, 0x18, - 0x6f, 0x70, 0x11, 0x70, 0x72, 0x03, 0x2f, 0x3f, 0x15, 0x52, 0x6b, 0xaa, 0x3e, 0xeb, 0xbd, 0x69, - 0x6a, 0xe9, 0x4b, 0xa3, 0x33, 0xfa, 0xde, 0x92, 0xf3, 0x78, 0x0d, 0x4b, 0xdb, 0xee, 0xee, 0xa9, - 0xe3, 0x2c, 0x66, 0xe9, 0x2a, 0x1b, 0xa7, 0xe4, 0xd7, 0x09, 0xbc, 0x78, 0x6c, 0xcb, 0x56, 0x1d, - 0x22, 0x2c, 0xb4, 0xac, 0x69, 0xdc, 0xe8, 0xf1, 0xc4, 0xe7, 0x64, 0xea, 0x13, 0xb4, 0x85, 0x71, - 0x9e, 0x9f, 0x0e, 0xda, 0x80, 0xf1, 0x1d, 0x5c, 0xa9, 0xc1, 0x5a, 0x28, 0x69, 0xa5, 0x2a, 0x7d, - 0xc7, 0x17, 0x31, 0x4b, 0x4f, 0xb3, 0x03, 0x1e, 0x63, 0xb8, 0xf4, 0xb2, 0xb9, 0x23, 0x2f, 0x94, - 0xd1, 0x7b, 0x7e, 0x16, 0xb3, 0xf4, 0x2c, 0x9b, 0x52, 0xf8, 0x16, 0xa2, 0x9d, 0x74, 0x24, 0xf6, - 0x44, 0xa2, 0x76, 0xd2, 0xf3, 0x65, 0x6f, 0x35, 0x27, 0x71, 0x0d, 0x17, 0x01, 0x37, 0xd2, 0x13, - 0x3f, 0x8f, 0x59, 0xca, 0xb2, 0xbf, 0x33, 0xa6, 0xf0, 0xcc, 0x97, 0x35, 0x89, 0xca, 0xa8, 0x7b, - 0x91, 0x53, 0xe5, 0x25, 0xbf, 0x88, 0x59, 0x1a, 0x65, 0xff, 0xd2, 0xe1, 0x56, 0x5d, 0x6a, 0x51, - 0xf8, 0x4a, 0x0d, 0xb7, 0x56, 0xc3, 0xad, 0x19, 0x99, 0xbc, 0x07, 0xfc, 0x6a, 0x49, 0x8f, 0xf1, - 0xfd, 0x2f, 0xe9, 0x2d, 0x5c, 0xcd, 0xd4, 0x21, 0x61, 0x0e, 0xe7, 0xfe, 0x41, 0x14, 0xd2, 0x15, - 0xa3, 0xf8, 0xcf, 0x88, 0x09, 0x3c, 0x35, 0xad, 0xb7, 0xad, 0x17, 0xa5, 0xce, 0xe9, 0xa1, 0x4f, - 0x3b, 0xca, 0x66, 0xdc, 0x87, 0x9f, 0x0c, 0xa2, 0xd1, 0x2e, 0x38, 0x53, 0x83, 0xdf, 0x00, 0x0f, - 0x7f, 0x26, 0xbe, 0xde, 0xf4, 0x5d, 0x39, 0x5a, 0x8e, 0xf5, 0xab, 0xe3, 0x02, 0x5b, 0x75, 0xc9, - 0x13, 0xfc, 0x08, 0x97, 0x93, 0x77, 0x47, 0x3e, 0xe8, 0x0f, 0x3f, 0x7e, 0x7d, 0xfd, 0xc8, 0x93, - 0xde, 0xe2, 0xf6, 0x0d, 0x3c, 0x2f, 0xcd, 0xe6, 0xae, 0xb1, 0x6a, 0x90, 0x38, 0x6a, 0x7e, 0x94, - 0x8a, 0x6e, 0x57, 0x5f, 0x9c, 0xcd, 0xb7, 0xa1, 0xc6, 0x5b, 0xb6, 0x5b, 0xf6, 0x7d, 0xbe, 0xf9, - 0x1d, 0x00, 0x00, 0xff, 0xff, 0x47, 0xd9, 0x92, 0x62, 0xdd, 0x02, 0x00, 0x00, -} diff --git a/rpc/lspd.proto b/rpc/lspd.proto index cdb16ba..e0792c5 100644 --- a/rpc/lspd.proto +++ b/rpc/lspd.proto @@ -10,6 +10,7 @@ service ChannelOpener { rpc ChannelInformation(ChannelInformationRequest) returns (ChannelInformationReply) {} rpc OpenChannel(OpenChannelRequest) returns (OpenChannelReply) {} + rpc RegisterPayment (RegisterPaymentRequest) returns (RegisterPaymentReply) {} } message ChannelInformationRequest { @@ -43,6 +44,11 @@ message ChannelInformationReply { /// The minimum value in millisatoshi we will require for incoming HTLCs on /// the channel. int64 min_htlc_msat = 9 [ json_name = "min_htlc_msat" ]; + + int64 channel_fee_start_amount = 10; + float channel_fee_rate = 11; + + bytes lsp_pubkey = 12; } message OpenChannelRequest { @@ -56,3 +62,15 @@ message OpenChannelReply { /// The output index uint32 output_index = 2 [ json_name = "output_index"]; } + +message RegisterPaymentRequest { + bytes blob = 3; +} +message RegisterPaymentReply {} +message PaymentInformation { + bytes payment_hash = 1; + bytes payment_secret = 2; + bytes destination = 3; + int64 incoming_amount_msat = 4; + int64 outgoing_amount_msat = 5; +} diff --git a/sample.env b/sample.env index 7b37a19..bf8407d 100644 --- a/sample.env +++ b/sample.env @@ -11,4 +11,5 @@ NODE_NAME= NODE_PUBKEY= NODE_HOST= -TOKEN= \ No newline at end of file +TOKEN= +LSPD_PRIVATE_KEY= \ No newline at end of file diff --git a/server.go b/server.go index 9890b80..b768683 100644 --- a/server.go +++ b/server.go @@ -5,13 +5,16 @@ import ( "crypto/tls" "crypto/x509" "encoding/hex" + "fmt" "log" "net" "os" "strings" lspdrpc "github.com/breez/lspd/rpc" + "github.com/golang/protobuf/proto" + "github.com/btcsuite/btcd/btcec" "github.com/btcsuite/btcd/chaincfg/chainhash" grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware" "github.com/lightningnetwork/lnd/lnrpc" @@ -25,12 +28,14 @@ import ( ) const ( - channelAmount = 1000000 - targetConf = 1 - minHtlcMsat = 600 - baseFeeMsat = 1000 - feeRate = 0.000001 - timeLockDelta = 144 + channelAmount = 1_000_000 + targetConf = 1 + minHtlcMsat = 600 + baseFeeMsat = 1000 + feeRate = 0.000001 + timeLockDelta = 144 + channelFeeStartAmount = 100_000 + channelFeeAmount = 0.001 ) type server struct{} @@ -38,22 +43,44 @@ type server struct{} var ( client lnrpc.LightningClient openChannelReqGroup singleflight.Group + privateKey *btcec.PrivateKey + publicKey *btcec.PublicKey ) func (s *server) ChannelInformation(ctx context.Context, in *lspdrpc.ChannelInformationRequest) (*lspdrpc.ChannelInformationReply, error) { return &lspdrpc.ChannelInformationReply{ - Name: os.Getenv("NODE_NAME"), - Pubkey: os.Getenv("NODE_PUBKEY"), - Host: os.Getenv("NODE_HOST"), - ChannelCapacity: channelAmount, - TargetConf: targetConf, - MinHtlcMsat: minHtlcMsat, - BaseFeeMsat: baseFeeMsat, - FeeRate: feeRate, - TimeLockDelta: timeLockDelta, + Name: os.Getenv("NODE_NAME"), + Pubkey: os.Getenv("NODE_PUBKEY"), + Host: os.Getenv("NODE_HOST"), + ChannelCapacity: channelAmount, + TargetConf: targetConf, + MinHtlcMsat: minHtlcMsat, + BaseFeeMsat: baseFeeMsat, + FeeRate: feeRate, + TimeLockDelta: timeLockDelta, + ChannelFeeStartAmount: channelFeeStartAmount, + ChannelFeeRate: channelFeeAmount, + LspPubkey: publicKey.SerializeCompressed(), }, nil } +func (s *server) RegisterPayment(ctx context.Context, in *lspdrpc.RegisterPaymentRequest) (*lspdrpc.RegisterPaymentReply, error) { + data, err := btcec.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) + //TODO put in the db + 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")) @@ -131,6 +158,21 @@ func getPendingNodeChannels(nodeID string) ([]*lnrpc.PendingChannelsResponse_Pen } func main() { + if len(os.Args) > 1 && os.Args[1] == "genkey" { + p, err := btcec.NewPrivateKey(btcec.S256()) + if err != nil { + log.Fatalf("btcec.NewPrivateKey() error: %v", err) + } + fmt.Printf("LSPD_PRIVATE_KEY=\"%x\"\n", p.Serialize()) + return + } + + 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(btcec.S256(), privateKeyBytes) + certmagicDomain := os.Getenv("CERTMAGIC_DOMAIN") address := os.Getenv("LISTEN_ADDRESS") var lis net.Listener From 3d5d4a528324cc83a18057850c4968e9c261ef54 Mon Sep 17 00:00:00 2001 From: Yaacov Akiba Slama Date: Sun, 2 Aug 2020 14:50:54 +0300 Subject: [PATCH 027/214] Update certmagic --- go.mod | 4 ++-- server.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 699447d..90a59e8 100644 --- a/go.mod +++ b/go.mod @@ -4,10 +4,10 @@ go 1.14 require ( github.com/btcsuite/btcd v0.20.0-beta + github.com/caddyserver/certmagic v0.11.2 github.com/golang/protobuf v1.4.2 github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 github.com/lightningnetwork/lnd v0.8.0-beta - github.com/mholt/certmagic v0.6.2 - golang.org/x/sync v0.0.0-20190423024810-112230192c58 + golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e google.golang.org/grpc v1.31.0 ) diff --git a/server.go b/server.go index b768683..9b1bcf3 100644 --- a/server.go +++ b/server.go @@ -16,9 +16,9 @@ import ( "github.com/btcsuite/btcd/btcec" "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/mholt/certmagic" "golang.org/x/sync/singleflight" "google.golang.org/grpc" "google.golang.org/grpc/codes" From 26cd383aa97d1acc9c5396ba391566488a66127d Mon Sep 17 00:00:00 2001 From: Yaacov Akiba Slama Date: Thu, 6 Aug 2020 16:24:29 +0300 Subject: [PATCH 028/214] Add intercept functionality to lspd to create channels on the fly. --- db.go | 71 ++++++++++++++++++++++++ go.mod | 9 ++- intercept.go | 154 +++++++++++++++++++++++++++++++++++++++++++++++++++ sample.env | 3 +- server.go | 18 +++++- 5 files changed, 250 insertions(+), 5 deletions(-) create mode 100644 db.go create mode 100644 intercept.go diff --git a/db.go b/db.go new file mode 100644 index 0000000..8283bc4 --- /dev/null +++ b/db.go @@ -0,0 +1,71 @@ +package main + +import ( + "context" + "fmt" + "log" + "os" + + "github.com/jackc/pgtype" + "github.com/jackc/pgx/v4" + "github.com/jackc/pgx/v4/pgxpool" +) + +var ( + pgxPool *pgxpool.Pool +) + +func pgConnect() error { + var err error + pgxPool, err = pgxpool.Connect(context.Background(), os.Getenv("DATABASE_URL")) + if err != nil { + return fmt.Errorf("pgxpool.Connect(%v): %w", os.Getenv("DATABASE_URL"), err) + } + return nil +} + +func paymentInfo(paymentHash []byte) ([]byte, []byte, int64, int64, []byte, int32, error) { + var ( + paymentSecret, destination []byte + incomingAmountMsat, outgoingAmountMsat int64 + fundingTxID []byte + fundingTxOutnum pgtype.Int4 + ) + err := pgxPool.QueryRow(context.Background(), + `SELECT payment_secret, destination, incoming_amount_msat, outgoing_amount_msat, funding_tx_id, funding_tx_outnum + FROM payments + WHERE payment_hash=$1`, + paymentHash).Scan(&paymentSecret, &destination, &incomingAmountMsat, &outgoingAmountMsat, &fundingTxID, &fundingTxOutnum) + if err != nil { + if err == pgx.ErrNoRows { + err = nil + } + return nil, nil, 0, 0, nil, 0, err + } + return paymentSecret, destination, incomingAmountMsat, outgoingAmountMsat, fundingTxID, fundingTxOutnum.Int, nil +} + +func setFundingTx(paymentHash, fundingTxID []byte, fundingTxOutnum int) error { + commandTag, err := pgxPool.Exec(context.Background(), + `UPDATE payments + SET funding_tx_id = $2, funding_tx_outnum = $3 + WHERE payment_hash=$1`, + paymentHash, fundingTxID, fundingTxOutnum) + log.Printf("setFundingTx(%x, %x, %v): %s err: %v", paymentHash, fundingTxID, fundingTxOutnum, commandTag, err) + return err +} + +func registerPayment(destination, paymentHash, paymentSecret []byte, incomingAmountMsat, outgoingAmountMsat int64) error { + commandTag, err := pgxPool.Exec(context.Background(), + `INSERT INTO + payments (destination, payment_hash, payment_secret, incoming_amount_msat, outgoing_amount_msat) + VALUES ($1, $2, $3, $4, $5)`, + destination, paymentHash, paymentSecret, incomingAmountMsat, outgoingAmountMsat) + log.Printf("registerPayment(%x, %x, %x, %v, %v) rows: %v err: %v", + destination, paymentHash, paymentSecret, incomingAmountMsat, outgoingAmountMsat, commandTag.RowsAffected(), err) + if err != nil { + return fmt.Errorf("registerPayment(%x, %x, %x, %v, %v) error: %w", + destination, paymentHash, paymentSecret, incomingAmountMsat, outgoingAmountMsat, err) + } + return nil +} diff --git a/go.mod b/go.mod index 90a59e8..8ef9190 100644 --- a/go.mod +++ b/go.mod @@ -3,11 +3,16 @@ module github.com/breez/lspd go 1.14 require ( - github.com/btcsuite/btcd v0.20.0-beta + github.com/btcsuite/btcd v0.20.1-beta github.com/caddyserver/certmagic v0.11.2 github.com/golang/protobuf v1.4.2 github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 - github.com/lightningnetwork/lnd v0.8.0-beta + github.com/jackc/pgtype v1.4.2 + github.com/jackc/pgx/v4 v4.8.1 + github.com/lightningnetwork/lightning-onion v1.0.1 + github.com/lightningnetwork/lnd v0.10.0-beta golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e google.golang.org/grpc v1.31.0 ) + +replace github.com/lightningnetwork/lnd v0.10.0-beta => github.com/breez/lnd v0.10.0-beta.rc6.0.20200727142715-f67a1052c0e0 diff --git a/intercept.go b/intercept.go new file mode 100644 index 0000000..ead87e8 --- /dev/null +++ b/intercept.go @@ -0,0 +1,154 @@ +package main + +import ( + "bytes" + "context" + "fmt" + "log" + "math/big" + "os" + + "github.com/btcsuite/btcd/btcec" + "github.com/lightningnetwork/lnd/lnrpc" + "github.com/lightningnetwork/lnd/lnrpc/routerrpc" + "github.com/lightningnetwork/lnd/lnwire" + "github.com/lightningnetwork/lnd/record" + "github.com/lightningnetwork/lnd/routing/route" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" + + sphinx "github.com/lightningnetwork/lightning-onion" +) + +func openChannel(ctx context.Context, client lnrpc.LightningClient, paymentHash, destination []byte, incomingAmountMsat int64) error { + capacity := incomingAmountMsat/1000 + channelFeeStartAmount + channelPoint, err := client.OpenChannelSync(ctx, &lnrpc.OpenChannelRequest{ + NodePubkey: destination, + LocalFundingAmount: capacity, + TargetConf: 20, + Private: true, + }) + if err != nil { + return err + } + err = setFundingTx(paymentHash, channelPoint.GetFundingTxidBytes(), int(channelPoint.OutputIndex)) + return err +} + +func getChannel(ctx context.Context, client lnrpc.LightningClient, node []byte) uint64 { + r, err := client.ListChannels(ctx, &lnrpc.ListChannelsRequest{Peer: node}) + if err != nil { + log.Printf("client.ListChannels(%x) error: %v", node, err) + return 0 + } + for _, c := range r.Channels { + log.Printf("getChannel(%x): %v", node, c.ChanId) + return c.ChanId + } + log.Printf("No channel found: getChannel(%x)", node) + return 0 +} + +func intercept() { + + clientCtx := metadata.AppendToOutgoingContext(context.Background(), "macaroon", os.Getenv("LND_MACAROON_HEX")) + interceptorClient, err := routerClient.HtlcInterceptor(clientCtx) + if err != nil { + log.Fatalf("routerClient.HtlcInterceptor(): %v", err) + } + + for { + request, err := interceptorClient.Recv() + if err != nil { + // If it is just the error result of the context cancellation + // the we exit silently. + status, ok := status.FromError(err) + if ok && status.Code() == codes.Canceled { + break + } + // Otherwise it an unexpected error, we fail the test. + log.Fatalf("unexpected error in interceptor.Recv() %v", err) + break + } + fmt.Printf("htlc: %v\nchanID: %v\nincoming amount: %v\noutgoing amount: %v\nincomin expiry: %v\noutgoing expiry: %v\npaymentHash: %x\nonionBlob: %x\n\n", + request.IncomingCircuitKey.HtlcId, + request.IncomingCircuitKey.ChanId, + request.IncomingAmountMsat, + request.OutgoingAmountMsat, + request.IncomingExpiry, + request.OutgoingExpiry, + request.PaymentHash, + request.OnionBlob, + ) + + paymentSecret, destination, incomingAmountMsat, outgoingAmountMsat, fundingTxID, _, err := paymentInfo(request.PaymentHash) + if err != nil { + log.Printf("paymentInfo(%x) error: %v", request.PaymentHash, err) + } + log.Printf("paymentSecret: %x\ndestination:%x\nincomingAmountMsat:%v\noutgoingAmountMsat:%v\n\n", + paymentSecret, destination, incomingAmountMsat, outgoingAmountMsat) + if paymentSecret != nil { + + if fundingTxID == nil { + err = openChannel(clientCtx, client, request.PaymentHash, destination, incomingAmountMsat) + log.Printf("openChannel(%x, %v) err: %v", destination, incomingAmountMsat, err) + //TODO: wait for the channel to be active + } + + pubKey, err := btcec.ParsePubKey(destination, btcec.S256()) + log.Printf("btcec.ParsePubKey(%x): %v", destination, err) + + sessionKey, err := btcec.NewPrivateKey(btcec.S256()) + log.Printf("btcec.NewPrivateKey(): %v", err) + + var bigProd, bigAmt big.Int + amt := (bigAmt.Div(bigProd.Mul(big.NewInt(outgoingAmountMsat), big.NewInt(int64(request.OutgoingAmountMsat))), big.NewInt(incomingAmountMsat))).Int64() + + var addr [32]byte + copy(addr[:], paymentSecret) + hop := route.Hop{ + AmtToForward: lnwire.MilliSatoshi(amt), + OutgoingTimeLock: request.OutgoingExpiry, + MPP: record.NewMPP(lnwire.MilliSatoshi(outgoingAmountMsat), addr), + CustomRecords: make(record.CustomSet), + } + + var b bytes.Buffer + err = hop.PackHopPayload(&b, uint64(0)) + log.Printf("hop.PackHopPayload(): %v", err) + + payload, err := sphinx.NewHopPayload(nil, b.Bytes()) + log.Printf("sphinx.NewHopPayload(): %v", err) + + var sphinxPath sphinx.PaymentPath + sphinxPath[0] = sphinx.OnionHop{ + NodePub: *pubKey, + HopPayload: payload, + } + sphinxPacket, err := sphinx.NewOnionPacket( + &sphinxPath, sessionKey, request.PaymentHash, + sphinx.DeterministicPacketFiller, + ) + var onionBlob bytes.Buffer + err = sphinxPacket.Encode(&onionBlob) + log.Printf("sphinxPacket.Encode(): %v", err) + interceptorClient.Send(&routerrpc.ForwardHtlcInterceptResponse{ + IncomingCircuitKey: request.IncomingCircuitKey, + Action: routerrpc.ResolveHoldForwardAction_RESUME, + OutgoingAmountMsat: uint64(amt), + OutgoingRequestedChanId: getChannel(clientCtx, client, destination), + OnionBlob: onionBlob.Bytes(), + }) + + } else { + interceptorClient.Send(&routerrpc.ForwardHtlcInterceptResponse{ + IncomingCircuitKey: request.IncomingCircuitKey, + Action: routerrpc.ResolveHoldForwardAction_RESUME, + OutgoingAmountMsat: request.OutgoingAmountMsat, + OutgoingRequestedChanId: request.OutgoingRequestedChanId, + OnionBlob: request.OnionBlob, + }) + } + } +} diff --git a/sample.env b/sample.env index bf8407d..523be08 100644 --- a/sample.env +++ b/sample.env @@ -12,4 +12,5 @@ NODE_PUBKEY= NODE_HOST= TOKEN= -LSPD_PRIVATE_KEY= \ No newline at end of file +LSPD_PRIVATE_KEY= +DATABASE_URL= \ No newline at end of file diff --git a/server.go b/server.go index 9b1bcf3..ed94a2e 100644 --- a/server.go +++ b/server.go @@ -19,6 +19,7 @@ import ( "github.com/caddyserver/certmagic" grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware" "github.com/lightningnetwork/lnd/lnrpc" + "github.com/lightningnetwork/lnd/lnrpc/routerrpc" "golang.org/x/sync/singleflight" "google.golang.org/grpc" "google.golang.org/grpc/codes" @@ -42,6 +43,7 @@ type server struct{} var ( client lnrpc.LightningClient + routerClient routerrpc.RouterClient openChannelReqGroup singleflight.Group privateKey *btcec.PrivateKey publicKey *btcec.PublicKey @@ -76,8 +78,13 @@ func (s *server) RegisterPayment(ctx context.Context, in *lspdrpc.RegisterPaymen 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) - //TODO put in the db + 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 = 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 } @@ -167,6 +174,11 @@ func main() { 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) @@ -207,6 +219,8 @@ func main() { } defer conn.Close() client = lnrpc.NewLightningClient(conn) + routerClient = routerrpc.NewRouterClient(conn) + go intercept() s := grpc.NewServer( grpc_middleware.WithUnaryServerChain(func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { From 987e6a1ff58b4615747d7e2e075e90c0ecf1b3ae Mon Sep 17 00:00:00 2001 From: Yaacov Akiba Slama Date: Tue, 11 Aug 2020 17:16:04 +0300 Subject: [PATCH 029/214] Wait for the new channel to be active before resuming the htlc --- db.go | 4 +-- intercept.go | 71 +++++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 58 insertions(+), 17 deletions(-) diff --git a/db.go b/db.go index 8283bc4..cdb5e36 100644 --- a/db.go +++ b/db.go @@ -24,7 +24,7 @@ func pgConnect() error { return nil } -func paymentInfo(paymentHash []byte) ([]byte, []byte, int64, int64, []byte, int32, error) { +func paymentInfo(paymentHash []byte) ([]byte, []byte, int64, int64, []byte, uint32, error) { var ( paymentSecret, destination []byte incomingAmountMsat, outgoingAmountMsat int64 @@ -42,7 +42,7 @@ func paymentInfo(paymentHash []byte) ([]byte, []byte, int64, int64, []byte, int3 } return nil, nil, 0, 0, nil, 0, err } - return paymentSecret, destination, incomingAmountMsat, outgoingAmountMsat, fundingTxID, fundingTxOutnum.Int, nil + return paymentSecret, destination, incomingAmountMsat, outgoingAmountMsat, fundingTxID, uint32(fundingTxOutnum.Int), nil } func setFundingTx(paymentHash, fundingTxID []byte, fundingTxOutnum int) error { diff --git a/intercept.go b/intercept.go index ead87e8..bf92c7b 100644 --- a/intercept.go +++ b/intercept.go @@ -7,6 +7,7 @@ import ( "log" "math/big" "os" + "time" "github.com/btcsuite/btcd/btcec" "github.com/lightningnetwork/lnd/lnrpc" @@ -21,7 +22,7 @@ import ( sphinx "github.com/lightningnetwork/lightning-onion" ) -func openChannel(ctx context.Context, client lnrpc.LightningClient, paymentHash, destination []byte, incomingAmountMsat int64) error { +func openChannel(ctx context.Context, client lnrpc.LightningClient, paymentHash, destination []byte, incomingAmountMsat int64) ([]byte, uint32, error) { capacity := incomingAmountMsat/1000 + channelFeeStartAmount channelPoint, err := client.OpenChannelSync(ctx, &lnrpc.OpenChannelRequest{ NodePubkey: destination, @@ -30,13 +31,13 @@ func openChannel(ctx context.Context, client lnrpc.LightningClient, paymentHash, Private: true, }) if err != nil { - return err + return nil, 0, err } err = setFundingTx(paymentHash, channelPoint.GetFundingTxidBytes(), int(channelPoint.OutputIndex)) - return err + return channelPoint.GetFundingTxidBytes(), channelPoint.OutputIndex, err } -func getChannel(ctx context.Context, client lnrpc.LightningClient, node []byte) uint64 { +func getChannel(ctx context.Context, client lnrpc.LightningClient, node []byte, channelPoint string) uint64 { r, err := client.ListChannels(ctx, &lnrpc.ListChannelsRequest{Peer: node}) if err != nil { log.Printf("client.ListChannels(%x) error: %v", node, err) @@ -44,7 +45,9 @@ func getChannel(ctx context.Context, client lnrpc.LightningClient, node []byte) } for _, c := range r.Channels { log.Printf("getChannel(%x): %v", node, c.ChanId) - return c.ChanId + if c.ChannelPoint == channelPoint && c.Active { + return c.ChanId + } } log.Printf("No channel found: getChannel(%x)", node) return 0 @@ -82,7 +85,7 @@ func intercept() { request.OnionBlob, ) - paymentSecret, destination, incomingAmountMsat, outgoingAmountMsat, fundingTxID, _, err := paymentInfo(request.PaymentHash) + paymentSecret, destination, incomingAmountMsat, outgoingAmountMsat, fundingTxID, fundingTxOutnum, err := paymentInfo(request.PaymentHash) if err != nil { log.Printf("paymentInfo(%x) error: %v", request.PaymentHash, err) } @@ -91,9 +94,14 @@ func intercept() { if paymentSecret != nil { if fundingTxID == nil { - err = openChannel(clientCtx, client, request.PaymentHash, destination, incomingAmountMsat) + fundingTxID, fundingTxOutnum, err = openChannel(clientCtx, client, request.PaymentHash, destination, incomingAmountMsat) log.Printf("openChannel(%x, %v) err: %v", destination, incomingAmountMsat, err) - //TODO: wait for the channel to be active + if err != nil { + interceptorClient.Send(&routerrpc.ForwardHtlcInterceptResponse{ + IncomingCircuitKey: request.IncomingCircuitKey, + Action: routerrpc.ResolveHoldForwardAction_FAIL, + }) + } } pubKey, err := btcec.ParsePubKey(destination, btcec.S256()) @@ -133,13 +141,11 @@ func intercept() { var onionBlob bytes.Buffer err = sphinxPacket.Encode(&onionBlob) log.Printf("sphinxPacket.Encode(): %v", err) - interceptorClient.Send(&routerrpc.ForwardHtlcInterceptResponse{ - IncomingCircuitKey: request.IncomingCircuitKey, - Action: routerrpc.ResolveHoldForwardAction_RESUME, - OutgoingAmountMsat: uint64(amt), - OutgoingRequestedChanId: getChannel(clientCtx, client, destination), - OnionBlob: onionBlob.Bytes(), - }) + channelPoint := fmt.Sprintf("%x:%v", fundingTxID, fundingTxOutnum) + go resumeOrCancel( + clientCtx, interceptorClient, request.IncomingCircuitKey, destination, + channelPoint, uint64(amt), onionBlob.Bytes(), + ) } else { interceptorClient.Send(&routerrpc.ForwardHtlcInterceptResponse{ @@ -152,3 +158,38 @@ func intercept() { } } } + +func resumeOrCancel( + ctx context.Context, + interceptorClient routerrpc.Router_HtlcInterceptorClient, + incomingCircuitKey *routerrpc.CircuitKey, + destination []byte, + channelPoint string, + outgoingAmountMsat uint64, + onionBlob []byte, +) { + deadline := time.Now().Add(10 * time.Second) + for { + chanID := getChannel(ctx, client, destination, channelPoint) + if chanID != 0 { + interceptorClient.Send(&routerrpc.ForwardHtlcInterceptResponse{ + IncomingCircuitKey: incomingCircuitKey, + Action: routerrpc.ResolveHoldForwardAction_RESUME, + OutgoingAmountMsat: outgoingAmountMsat, + OutgoingRequestedChanId: chanID, + OnionBlob: onionBlob, + }) + return + } + log.Printf("getChannel(%x, %v) returns 0", destination, channelPoint) + if time.Now().After(deadline) { + log.Printf("Stop retrying getChannel(%x, %v)", destination, channelPoint) + break + } + time.Sleep(1 * time.Second) + } + interceptorClient.Send(&routerrpc.ForwardHtlcInterceptResponse{ + IncomingCircuitKey: incomingCircuitKey, + Action: routerrpc.ResolveHoldForwardAction_FAIL, + }) +} From 5ec237e4a44f10d5a97ab132176a587ac3137aee Mon Sep 17 00:00:00 2001 From: Yaacov Akiba Slama Date: Tue, 11 Aug 2020 19:26:48 +0300 Subject: [PATCH 030/214] Fix channelPoint to string --- intercept.go | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/intercept.go b/intercept.go index bf92c7b..60d34a0 100644 --- a/intercept.go +++ b/intercept.go @@ -10,6 +10,8 @@ import ( "time" "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/routerrpc" "github.com/lightningnetwork/lnd/lnwire" @@ -101,6 +103,7 @@ func intercept() { IncomingCircuitKey: request.IncomingCircuitKey, Action: routerrpc.ResolveHoldForwardAction_FAIL, }) + continue } } @@ -141,7 +144,17 @@ func intercept() { var onionBlob bytes.Buffer err = sphinxPacket.Encode(&onionBlob) log.Printf("sphinxPacket.Encode(): %v", err) - channelPoint := fmt.Sprintf("%x:%v", fundingTxID, fundingTxOutnum) + var h chainhash.Hash + err = h.SetBytes(fundingTxID) + if err != nil { + log.Printf("h.SetBytes(%x) error: %v", fundingTxID, err) + interceptorClient.Send(&routerrpc.ForwardHtlcInterceptResponse{ + IncomingCircuitKey: request.IncomingCircuitKey, + Action: routerrpc.ResolveHoldForwardAction_FAIL, + }) + continue + } + channelPoint := wire.NewOutPoint(&h, fundingTxOutnum).String() go resumeOrCancel( clientCtx, interceptorClient, request.IncomingCircuitKey, destination, channelPoint, uint64(amt), onionBlob.Bytes(), From a6c770462da53169a11633e53b54fe029f9f7278 Mon Sep 17 00:00:00 2001 From: Yaacov Akiba Slama Date: Sun, 16 Aug 2020 12:15:02 +0300 Subject: [PATCH 031/214] Add postgresql migration files --- postgresql/migrations/000000_.down.sql | 0 postgresql/migrations/000000_.up.sql | 0 postgresql/migrations/000001_create_paymens_table.up.sql | 5 +++++ postgresql/migrations/000001_create_payments_table.down.sql | 1 + postgresql/migrations/000002_details.down.sql | 5 +++++ postgresql/migrations/000002_details.up.sql | 5 +++++ postgresql/migrations/000003_funding_tx.down.sql | 2 ++ postgresql/migrations/000003_funding_tx.up.sql | 2 ++ 8 files changed, 20 insertions(+) create mode 100644 postgresql/migrations/000000_.down.sql create mode 100644 postgresql/migrations/000000_.up.sql create mode 100644 postgresql/migrations/000001_create_paymens_table.up.sql create mode 100644 postgresql/migrations/000001_create_payments_table.down.sql create mode 100644 postgresql/migrations/000002_details.down.sql create mode 100644 postgresql/migrations/000002_details.up.sql create mode 100644 postgresql/migrations/000003_funding_tx.down.sql create mode 100644 postgresql/migrations/000003_funding_tx.up.sql diff --git a/postgresql/migrations/000000_.down.sql b/postgresql/migrations/000000_.down.sql new file mode 100644 index 0000000..e69de29 diff --git a/postgresql/migrations/000000_.up.sql b/postgresql/migrations/000000_.up.sql new file mode 100644 index 0000000..e69de29 diff --git a/postgresql/migrations/000001_create_paymens_table.up.sql b/postgresql/migrations/000001_create_paymens_table.up.sql new file mode 100644 index 0000000..19cbdeb --- /dev/null +++ b/postgresql/migrations/000001_create_paymens_table.up.sql @@ -0,0 +1,5 @@ +CREATE TABLE public.payments ( + payment_hash bytea NOT NULL, + payment_request_out varchar NOT NULL, + CONSTRAINT payments_pkey PRIMARY KEY (payment_hash) +); \ No newline at end of file diff --git a/postgresql/migrations/000001_create_payments_table.down.sql b/postgresql/migrations/000001_create_payments_table.down.sql new file mode 100644 index 0000000..8baef0d --- /dev/null +++ b/postgresql/migrations/000001_create_payments_table.down.sql @@ -0,0 +1 @@ +DROP TABLE public.payments; \ No newline at end of file diff --git a/postgresql/migrations/000002_details.down.sql b/postgresql/migrations/000002_details.down.sql new file mode 100644 index 0000000..b559ed4 --- /dev/null +++ b/postgresql/migrations/000002_details.down.sql @@ -0,0 +1,5 @@ +ALTER TABLE public.payments DROP COLUMN payment_secret; +ALTER TABLE public.payments DROP COLUMN destination; +ALTER TABLE public.payments DROP COLUMN incoming_amount_msat; +ALTER TABLE public.payments DROP COLUMN outgoing_amount_msat; +ALTER TABLE public.payments ADD payment_request_out varchar NOT NULL; \ No newline at end of file diff --git a/postgresql/migrations/000002_details.up.sql b/postgresql/migrations/000002_details.up.sql new file mode 100644 index 0000000..68d95fa --- /dev/null +++ b/postgresql/migrations/000002_details.up.sql @@ -0,0 +1,5 @@ +ALTER TABLE public.payments DROP COLUMN payment_request_out; +ALTER TABLE public.payments ADD payment_secret bytea NOT NULL; +ALTER TABLE public.payments ADD destination bytea NOT NULL; +ALTER TABLE public.payments ADD incoming_amount_msat bigint NOT NULL; +ALTER TABLE public.payments ADD outgoing_amount_msat bigint NOT NULL; \ No newline at end of file diff --git a/postgresql/migrations/000003_funding_tx.down.sql b/postgresql/migrations/000003_funding_tx.down.sql new file mode 100644 index 0000000..c6da088 --- /dev/null +++ b/postgresql/migrations/000003_funding_tx.down.sql @@ -0,0 +1,2 @@ +ALTER TABLE public.payments DROP COLUMN funding_tx_id; +ALTER TABLE public.payments DROP COLUMN funding_tx_outnum; \ No newline at end of file diff --git a/postgresql/migrations/000003_funding_tx.up.sql b/postgresql/migrations/000003_funding_tx.up.sql new file mode 100644 index 0000000..df40ee6 --- /dev/null +++ b/postgresql/migrations/000003_funding_tx.up.sql @@ -0,0 +1,2 @@ +ALTER TABLE public.payments ADD funding_tx_id bytea NULL; +ALTER TABLE public.payments ADD funding_tx_outnum int NULL; \ No newline at end of file From a8d226abb8f27d9523c1328889fb1618fe2d5d41 Mon Sep 17 00:00:00 2001 From: Yaacov Akiba Slama Date: Tue, 18 Aug 2020 17:03:18 +0300 Subject: [PATCH 032/214] Obtain node name and pubkey from the node itself if not found in env --- server.go | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/server.go b/server.go index ed94a2e..4a06de3 100644 --- a/server.go +++ b/server.go @@ -47,12 +47,14 @@ var ( 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: os.Getenv("NODE_NAME"), - Pubkey: os.Getenv("NODE_PUBKEY"), + Name: nodeName, + Pubkey: nodePubkey, Host: os.Getenv("NODE_HOST"), ChannelCapacity: channelAmount, TargetConf: targetConf, @@ -220,6 +222,19 @@ func main() { defer conn.Close() client = lnrpc.NewLightningClient(conn) routerClient = routerrpc.NewRouterClient(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() s := grpc.NewServer( From 630c6c3803c31c605055f733a595492dc6269c2a Mon Sep 17 00:00:00 2001 From: Yaacov Akiba Slama Date: Tue, 25 Aug 2020 17:19:35 +0300 Subject: [PATCH 033/214] Send email notification (using aws ses) when a channel is created --- email.go | 137 +++++++++++++++++++++++++++++++++++++++++++++++++++ go.mod | 1 + intercept.go | 9 ++++ sample.env | 10 +++- 4 files changed, 156 insertions(+), 1 deletion(-) create mode 100644 email.go diff --git a/email.go b/email.go new file mode 100644 index 0000000..3277a48 --- /dev/null +++ b/email.go @@ -0,0 +1,137 @@ +package main + +import ( + "bytes" + "encoding/hex" + "encoding/json" + "fmt" + "html/template" + "log" + "os" + "strconv" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/awserr" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/ses" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" +) + +const ( + charset = "UTF-8" +) + +func addresses(a string) (addr []*string) { + json.Unmarshal([]byte(a), &addr) + return +} + +func sendEmail(to, cc, from, content, subject string) error { + + sess, err := session.NewSession(&aws.Config{}) + if err != nil { + log.Printf("Error in session.NewSession: %v", err) + return err + } + svc := ses.New(sess) + + input := &ses.SendEmailInput{ + Destination: &ses.Destination{ + CcAddresses: addresses(cc), + ToAddresses: addresses(to), + }, + Message: &ses.Message{ + Body: &ses.Body{ + Html: &ses.Content{ + Charset: aws.String(charset), + Data: aws.String(content), + }, + }, + Subject: &ses.Content{ + Charset: aws.String(charset), + Data: aws.String(subject), + }, + }, + Source: aws.String(from), + } + // Attempt to send the email. + result, err := svc.SendEmail(input) + if err != nil { + if aerr, ok := err.(awserr.Error); ok { + switch aerr.Code() { + case ses.ErrCodeMessageRejected: + log.Println(ses.ErrCodeMessageRejected, aerr.Error()) + case ses.ErrCodeMailFromDomainNotVerifiedException: + log.Println(ses.ErrCodeMailFromDomainNotVerifiedException, aerr.Error()) + case ses.ErrCodeConfigurationSetDoesNotExistException: + log.Println(ses.ErrCodeConfigurationSetDoesNotExistException, aerr.Error()) + default: + log.Println(aerr.Error()) + } + } else { + // Print the error, cast err to awserr.Error to get the Code and + // Message from an error. + log.Println(err.Error()) + } + return err + } + + log.Printf("Email sent with result:\n%v", result) + + return nil +} + +func sendOpenChannelEmailNotification( + paymentHash []byte, incomingAmountMsat int64, + destination []byte, capacity int64, + fundingTxID []byte, fundingTxOutnum uint32) error { + + var h chainhash.Hash + err := h.SetBytes(fundingTxID) + if err != nil { + log.Printf("h.SetBytes(%x) error: %v", fundingTxID, err) + return fmt.Errorf("h.SetBytes(%x) error: %w", fundingTxID, err) + } + channelPoint := wire.NewOutPoint(&h, fundingTxOutnum).String() + + var html bytes.Buffer + + tpl := ` + + + + + + +
Payment Hash:{{ .PaymentHash }}
Incoming Amount (msat):{{ .IncomingAmountMsat }}
Destination Node:{{ .Destination }}
Channel capacity (sat):{{ .Capacity }}
Channel point:{{ .ChannelPoint }}
+ ` + t, err := template.New("OpenChannelEmail").Parse(tpl) + if err != nil { + return err + } + + if err := t.Execute(&html, map[string]string{ + "PaymentHash": hex.EncodeToString(paymentHash), + "IncomingAmountMsat": strconv.FormatUint(uint64(incomingAmountMsat), 10), + "Destination": hex.EncodeToString(destination), + "Capacity": strconv.FormatUint(uint64(capacity), 10), + "ChannelPoint": channelPoint, + }); err != nil { + return err + } + + err = sendEmail( + os.Getenv("OPENCHANNEL_NOTIFICATION_TO"), + os.Getenv("OPENCHANNEL_NOTIFICATION_CC"), + os.Getenv("OPENCHANNEL_NOTIFICATION_FROM"), + html.String(), + "Open Channel - Interceptor", + ) + if err != nil { + log.Printf("Error sending open channel email: %v", err) + return err + } + + return nil +} diff --git a/go.mod b/go.mod index 8ef9190..a8ee50b 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/breez/lspd go 1.14 require ( + github.com/aws/aws-sdk-go v1.30.20 github.com/btcsuite/btcd v0.20.1-beta github.com/caddyserver/certmagic v0.11.2 github.com/golang/protobuf v1.4.2 diff --git a/intercept.go b/intercept.go index 60d34a0..99f708e 100644 --- a/intercept.go +++ b/intercept.go @@ -33,8 +33,17 @@ func openChannel(ctx context.Context, client lnrpc.LightningClient, paymentHash, Private: true, }) if err != nil { + log.Printf("client.OpenChannelSync(%x, %v) error: %v", destination, capacity, err) return nil, 0, err } + sendOpenChannelEmailNotification( + paymentHash, + incomingAmountMsat, + destination, + capacity, + channelPoint.GetFundingTxidBytes(), + channelPoint.OutputIndex, + ) err = setFundingTx(paymentHash, channelPoint.GetFundingTxidBytes(), int(channelPoint.OutputIndex)) return channelPoint.GetFundingTxidBytes(), channelPoint.OutputIndex, err } diff --git a/sample.env b/sample.env index 523be08..ee103b9 100644 --- a/sample.env +++ b/sample.env @@ -13,4 +13,12 @@ NODE_HOST= TOKEN= LSPD_PRIVATE_KEY= -DATABASE_URL= \ No newline at end of file +DATABASE_URL= + +AWS_REGION= +AWS_ACCESS_KEY_ID= +AWS_SECRET_ACCESS_KEY= + +OPENCHANNEL_NOTIFICATION_TO='["Name1 "]' +OPENCHANNEL_NOTIFICATION_CC='["Name2 ","Name3 "]' +OPENCHANNEL_NOTIFICATION_FROM="Name4 " \ No newline at end of file From 5e4f1a1aeb10f95845c5a3c911d644f76de80a58 Mon Sep 17 00:00:00 2001 From: Yaacov Akiba Slama Date: Mon, 31 Aug 2020 16:03:39 +0300 Subject: [PATCH 034/214] Check the amounts when using RegisterPayments --- intercept.go | 11 +++++++++++ server.go | 24 +++++++++++++++--------- 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/intercept.go b/intercept.go index 99f708e..0fd5b92 100644 --- a/intercept.go +++ b/intercept.go @@ -24,6 +24,17 @@ import ( sphinx "github.com/lightningnetwork/lightning-onion" ) +func checkPayment(incomingAmountMsat, outgoingAmountMsat int64) error { + var fees int64 = 0 + if incomingAmountMsat > channelFeeStartAmount { + fees += (incomingAmountMsat - channelFeeStartAmount) * channelFeeAmountNumerator / channelFeeAmountDenominator + } + if incomingAmountMsat-outgoingAmountMsat < fees { + return fmt.Errorf("not enough fees") + } + return nil +} + func openChannel(ctx context.Context, client lnrpc.LightningClient, paymentHash, destination []byte, incomingAmountMsat int64) ([]byte, uint32, error) { capacity := incomingAmountMsat/1000 + channelFeeStartAmount channelPoint, err := client.OpenChannelSync(ctx, &lnrpc.OpenChannelRequest{ diff --git a/server.go b/server.go index 4a06de3..868ae8d 100644 --- a/server.go +++ b/server.go @@ -29,14 +29,15 @@ import ( ) const ( - channelAmount = 1_000_000 - targetConf = 1 - minHtlcMsat = 600 - baseFeeMsat = 1000 - feeRate = 0.000001 - timeLockDelta = 144 - channelFeeStartAmount = 100_000 - channelFeeAmount = 0.001 + channelAmount = 1_000_000 + targetConf = 1 + minHtlcMsat = 600 + baseFeeMsat = 1000 + feeRate = 0.000001 + timeLockDelta = 144 + channelFeeStartAmount = 100_000 + channelFeeAmountNumerator = 1 + channelFeeAmountDenominator = 1000 ) type server struct{} @@ -63,7 +64,7 @@ func (s *server) ChannelInformation(ctx context.Context, in *lspdrpc.ChannelInfo FeeRate: feeRate, TimeLockDelta: timeLockDelta, ChannelFeeStartAmount: channelFeeStartAmount, - ChannelFeeRate: channelFeeAmount, + ChannelFeeRate: 1.0 * channelFeeAmountNumerator / channelFeeAmountDenominator, LspPubkey: publicKey.SerializeCompressed(), }, nil } @@ -82,6 +83,11 @@ func (s *server) RegisterPayment(ctx context.Context, in *lspdrpc.RegisterPaymen } 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) From 980d2b40e5b0af7071d57dc4a08c9cc3cf48150b Mon Sep 17 00:00:00 2001 From: Yaacov Akiba Slama Date: Wed, 9 Sep 2020 17:41:12 +0300 Subject: [PATCH 035/214] Remove channel_fee_start_amount and add channel_fee_permyriad --- intercept.go | 7 +--- rpc/lspd.pb.go | 99 +++++++++++++++++++++++--------------------------- rpc/lspd.proto | 5 +-- server.go | 40 ++++++++++---------- 4 files changed, 68 insertions(+), 83 deletions(-) diff --git a/intercept.go b/intercept.go index 0fd5b92..53e960c 100644 --- a/intercept.go +++ b/intercept.go @@ -25,10 +25,7 @@ import ( ) func checkPayment(incomingAmountMsat, outgoingAmountMsat int64) error { - var fees int64 = 0 - if incomingAmountMsat > channelFeeStartAmount { - fees += (incomingAmountMsat - channelFeeStartAmount) * channelFeeAmountNumerator / channelFeeAmountDenominator - } + fees := incomingAmountMsat * channelFeePermyriad / 10_000 / 1_000 * 1_000 if incomingAmountMsat-outgoingAmountMsat < fees { return fmt.Errorf("not enough fees") } @@ -36,7 +33,7 @@ func checkPayment(incomingAmountMsat, outgoingAmountMsat int64) error { } func openChannel(ctx context.Context, client lnrpc.LightningClient, paymentHash, destination []byte, incomingAmountMsat int64) ([]byte, uint32, error) { - capacity := incomingAmountMsat/1000 + channelFeeStartAmount + capacity := incomingAmountMsat/1000 + additionalChannelCapacity channelPoint, err := client.OpenChannelSync(ctx, &lnrpc.OpenChannelRequest{ NodePubkey: destination, LocalFundingAmount: capacity, diff --git a/rpc/lspd.pb.go b/rpc/lspd.pb.go index 8b351ea..fe84bee 100644 --- a/rpc/lspd.pb.go +++ b/rpc/lspd.pb.go @@ -86,13 +86,12 @@ type ChannelInformationReply struct { TimeLockDelta uint32 `protobuf:"varint,8,opt,name=time_lock_delta,proto3" json:"time_lock_delta,omitempty"` /// The minimum value in millisatoshi we will require for incoming HTLCs on /// the channel. - MinHtlcMsat int64 `protobuf:"varint,9,opt,name=min_htlc_msat,proto3" json:"min_htlc_msat,omitempty"` - ChannelFeeStartAmount int64 `protobuf:"varint,10,opt,name=channel_fee_start_amount,json=channelFeeStartAmount,proto3" json:"channel_fee_start_amount,omitempty"` - ChannelFeeRate float32 `protobuf:"fixed32,11,opt,name=channel_fee_rate,json=channelFeeRate,proto3" json:"channel_fee_rate,omitempty"` - LspPubkey []byte `protobuf:"bytes,12,opt,name=lsp_pubkey,json=lspPubkey,proto3" json:"lsp_pubkey,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + MinHtlcMsat int64 `protobuf:"varint,9,opt,name=min_htlc_msat,proto3" json:"min_htlc_msat,omitempty"` + ChannelFeePermyriad int64 `protobuf:"varint,10,opt,name=channel_fee_permyriad,json=channelFeePermyriad,proto3" json:"channel_fee_permyriad,omitempty"` + LspPubkey []byte `protobuf:"bytes,11,opt,name=lsp_pubkey,json=lspPubkey,proto3" json:"lsp_pubkey,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } func (m *ChannelInformationReply) Reset() { *m = ChannelInformationReply{} } @@ -183,16 +182,9 @@ func (m *ChannelInformationReply) GetMinHtlcMsat() int64 { return 0 } -func (m *ChannelInformationReply) GetChannelFeeStartAmount() int64 { +func (m *ChannelInformationReply) GetChannelFeePermyriad() int64 { if m != nil { - return m.ChannelFeeStartAmount - } - return 0 -} - -func (m *ChannelInformationReply) GetChannelFeeRate() float32 { - if m != nil { - return m.ChannelFeeRate + return m.ChannelFeePermyriad } return 0 } @@ -449,44 +441,43 @@ func init() { } var fileDescriptor_c69a0f5a734bca26 = []byte{ - // 586 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x84, 0x54, 0xdd, 0x6e, 0xd3, 0x4c, - 0x10, 0xfd, 0xdc, 0xa6, 0x3f, 0x99, 0x38, 0x6d, 0x35, 0xca, 0x17, 0x4c, 0x44, 0x85, 0x09, 0x20, - 0x59, 0xa8, 0xaa, 0x2a, 0x7a, 0xc1, 0x75, 0x8b, 0x84, 0x40, 0xa2, 0x22, 0xda, 0x4a, 0xdc, 0x5a, - 0x1b, 0x67, 0x9a, 0x58, 0xb5, 0x77, 0x8d, 0x77, 0x83, 0x9a, 0x67, 0xe3, 0x75, 0x78, 0x09, 0xee, - 0xd0, 0xfe, 0xa4, 0x71, 0x9a, 0x56, 0xdc, 0xcd, 0x9e, 0x39, 0x33, 0x7b, 0x3c, 0x67, 0xd6, 0x00, - 0x85, 0xaa, 0x26, 0xa7, 0x55, 0x2d, 0xb5, 0xc4, 0x96, 0x89, 0x87, 0xe7, 0xf0, 0xfc, 0xe3, 0x8c, - 0x0b, 0x41, 0xc5, 0x17, 0x71, 0x23, 0xeb, 0x92, 0xeb, 0x5c, 0x0a, 0x46, 0x3f, 0xe6, 0xa4, 0x34, - 0xf6, 0x61, 0xb7, 0x9a, 0x8f, 0x6f, 0x69, 0x11, 0x05, 0x71, 0x90, 0xb4, 0x99, 0x3f, 0x0d, 0x7f, - 0x6d, 0xc3, 0xb3, 0xc7, 0xaa, 0xaa, 0x62, 0x81, 0x08, 0x2d, 0xc1, 0x4b, 0xf2, 0x15, 0x36, 0x6e, - 0xf4, 0xd9, 0x6a, 0xf6, 0x31, 0xdc, 0x99, 0x54, 0x3a, 0xda, 0x76, 0x5c, 0x13, 0xe3, 0x3b, 0x38, - 0xca, 0x5c, 0xeb, 0x34, 0xe3, 0x15, 0xcf, 0x72, 0xbd, 0x88, 0x5a, 0x71, 0x90, 0x6c, 0xb3, 0x0d, - 0x1c, 0x63, 0xe8, 0x68, 0x5e, 0x4f, 0x49, 0xa7, 0x99, 0x14, 0x37, 0xd1, 0x4e, 0x1c, 0x24, 0x3b, - 0xac, 0x09, 0xe1, 0x1b, 0xe8, 0x8e, 0xb9, 0xa2, 0xf4, 0x86, 0x28, 0x2d, 0x15, 0xd7, 0xd1, 0xae, - 0x6d, 0xb5, 0x0e, 0xe2, 0x00, 0xf6, 0x4d, 0x5c, 0x73, 0x4d, 0xd1, 0x5e, 0x1c, 0x24, 0x01, 0xbb, - 0x3f, 0x63, 0x02, 0x87, 0x3a, 0x2f, 0x29, 0x2d, 0x64, 0x76, 0x9b, 0x4e, 0xa8, 0xd0, 0x3c, 0xda, - 0x8f, 0x83, 0xa4, 0xcb, 0x1e, 0xc2, 0xe6, 0xae, 0x32, 0x17, 0xe9, 0x4c, 0x17, 0x99, 0xbb, 0xab, - 0xed, 0xee, 0x5a, 0x03, 0xf1, 0x03, 0x44, 0xcb, 0xef, 0x30, 0x77, 0x28, 0xcd, 0x6b, 0x9d, 0xf2, - 0x52, 0xce, 0x85, 0x8e, 0xc0, 0x16, 0xfc, 0xef, 0xf3, 0x9f, 0x88, 0xae, 0x4d, 0xf6, 0xc2, 0x26, - 0x31, 0x59, 0x0d, 0xe6, 0x5e, 0x6c, 0x27, 0x0e, 0x92, 0x2d, 0x76, 0xb0, 0x2a, 0x60, 0x46, 0xf2, - 0xb1, 0xf5, 0x39, 0xf5, 0x23, 0x0f, 0xe3, 0x20, 0x09, 0x59, 0xbb, 0x50, 0xd5, 0xc8, 0xb9, 0x77, - 0x02, 0xf8, 0xad, 0x22, 0xe1, 0x0d, 0xfc, 0x97, 0xd7, 0x23, 0x38, 0x5a, 0x63, 0x1b, 0x8f, 0x23, - 0xd8, 0xd3, 0x77, 0xe9, 0x8c, 0xab, 0x99, 0x27, 0x2f, 0x8f, 0x38, 0x84, 0x50, 0xce, 0x75, 0x35, - 0xd7, 0x69, 0x2e, 0x26, 0x74, 0x67, 0xfd, 0xee, 0xb2, 0x35, 0x6c, 0x78, 0x02, 0x7d, 0x46, 0xd3, - 0x5c, 0x69, 0xaa, 0x47, 0x7c, 0x51, 0x92, 0xd0, 0x4b, 0x0d, 0x08, 0xad, 0x71, 0x21, 0xc7, 0x76, - 0x1f, 0x42, 0x66, 0xe3, 0x61, 0x1f, 0x7a, 0x1b, 0xec, 0xaa, 0x58, 0x0c, 0x7f, 0x07, 0x80, 0x1e, - 0x68, 0xec, 0x20, 0xbe, 0x82, 0xb0, 0x72, 0xe8, 0x4a, 0x5f, 0xc8, 0x3a, 0x1e, 0xfb, 0x6c, 0x34, - 0xbe, 0x85, 0x83, 0x25, 0x45, 0x51, 0x56, 0x93, 0xb6, 0x2a, 0x43, 0xd6, 0xf5, 0xe8, 0xb5, 0x05, - 0xcd, 0x72, 0x4d, 0x48, 0xe9, 0x5c, 0xd8, 0xc6, 0x5e, 0x53, 0x13, 0xc2, 0x33, 0xe8, 0xe5, 0x22, - 0x93, 0x65, 0x2e, 0xa6, 0xde, 0x41, 0xe7, 0xbb, 0x5b, 0x57, 0x5c, 0xe6, 0x9c, 0x7f, 0x57, 0xc6, - 0xfc, 0x33, 0xe8, 0xc9, 0xb9, 0x9e, 0xca, 0x87, 0x15, 0x3b, 0xae, 0x62, 0x99, 0x5b, 0x55, 0xbc, - 0xff, 0x13, 0x40, 0xd7, 0xcf, 0xde, 0xd8, 0x40, 0x35, 0x7e, 0x07, 0xdc, 0x7c, 0x7b, 0xf8, 0xf2, - 0xd4, 0x3e, 0xed, 0x27, 0xdf, 0xf2, 0xe0, 0xf8, 0x69, 0x82, 0x19, 0xe7, 0x7f, 0x78, 0x01, 0x9d, - 0x86, 0xd1, 0x18, 0x39, 0xfe, 0xe6, 0xa6, 0x0c, 0xfa, 0x8f, 0x64, 0x5c, 0x8b, 0x2b, 0x38, 0x7c, - 0xe0, 0x15, 0xbe, 0x70, 0xe4, 0xc7, 0x0d, 0x1f, 0x0c, 0x9e, 0xc8, 0xda, 0x76, 0x97, 0xaf, 0xa1, - 0x97, 0xcb, 0xd3, 0x69, 0x5d, 0x65, 0x8e, 0xa6, 0xa8, 0xfe, 0x99, 0x67, 0x74, 0xd9, 0xfe, 0xaa, - 0xaa, 0xc9, 0xc8, 0xfc, 0xc4, 0x46, 0xc1, 0x78, 0xd7, 0xfe, 0xcd, 0xce, 0xff, 0x06, 0x00, 0x00, - 0xff, 0xff, 0xd7, 0x15, 0xf2, 0x09, 0xdb, 0x04, 0x00, 0x00, + // 568 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x84, 0x54, 0xff, 0x6e, 0xd3, 0x30, + 0x10, 0x26, 0xac, 0xfb, 0xd1, 0x6b, 0xcb, 0xa6, 0xa3, 0x94, 0x50, 0x31, 0x11, 0x0a, 0x48, 0x11, + 0x9a, 0xa6, 0x69, 0x7b, 0x82, 0x0d, 0x09, 0x81, 0xc4, 0x44, 0x65, 0x24, 0xfe, 0x8d, 0xdc, 0xf4, + 0xd6, 0x5a, 0x4b, 0x6c, 0x13, 0xbb, 0x68, 0x7d, 0x07, 0x1e, 0x93, 0x97, 0xe0, 0x3f, 0x64, 0x3b, + 0x61, 0xed, 0xba, 0x89, 0xff, 0xce, 0xdf, 0x7d, 0xf7, 0xdd, 0xc5, 0xdf, 0x39, 0x00, 0x85, 0xd1, + 0xd3, 0x63, 0x5d, 0x29, 0xab, 0xb0, 0xe5, 0xe2, 0xd1, 0x19, 0xbc, 0xf8, 0x30, 0xe7, 0x52, 0x52, + 0xf1, 0x59, 0x5e, 0xa9, 0xaa, 0xe4, 0x56, 0x28, 0xc9, 0xe8, 0xc7, 0x82, 0x8c, 0xc5, 0x01, 0xec, + 0xe8, 0xc5, 0xe4, 0x9a, 0x96, 0x71, 0x94, 0x44, 0x69, 0x9b, 0xd5, 0xa7, 0xd1, 0xaf, 0x2d, 0x78, + 0x7e, 0x5f, 0x95, 0x2e, 0x96, 0x88, 0xd0, 0x92, 0xbc, 0xa4, 0xba, 0xc2, 0xc7, 0x2b, 0x3a, 0x8f, + 0x57, 0x75, 0x1c, 0x77, 0xae, 0x8c, 0x8d, 0xb7, 0x02, 0xd7, 0xc5, 0xf8, 0x1e, 0x0e, 0xf2, 0x20, + 0x9d, 0xe5, 0x5c, 0xf3, 0x5c, 0xd8, 0x65, 0xdc, 0x4a, 0xa2, 0x74, 0x8b, 0x6d, 0xe0, 0x98, 0x40, + 0xc7, 0xf2, 0x6a, 0x46, 0x36, 0xcb, 0x95, 0xbc, 0x8a, 0xb7, 0x93, 0x28, 0xdd, 0x66, 0xab, 0x10, + 0xbe, 0x85, 0xde, 0x84, 0x1b, 0xca, 0xae, 0x88, 0xb2, 0xd2, 0x70, 0x1b, 0xef, 0x78, 0xa9, 0x75, + 0x10, 0x87, 0xb0, 0xe7, 0xe2, 0x8a, 0x5b, 0x8a, 0x77, 0x93, 0x28, 0x8d, 0xd8, 0xbf, 0x33, 0xa6, + 0xb0, 0x6f, 0x45, 0x49, 0x59, 0xa1, 0xf2, 0xeb, 0x6c, 0x4a, 0x85, 0xe5, 0xf1, 0x5e, 0x12, 0xa5, + 0x3d, 0x76, 0x17, 0x76, 0xbd, 0x4a, 0x21, 0xb3, 0xb9, 0x2d, 0xf2, 0xd0, 0xab, 0x1d, 0x7a, 0xad, + 0x81, 0x78, 0x0a, 0xcf, 0x9a, 0xef, 0x70, 0x3d, 0x34, 0x55, 0xe5, 0xb2, 0x12, 0x7c, 0x1a, 0x83, + 0x67, 0x3f, 0xad, 0x93, 0x1f, 0x89, 0xc6, 0x4d, 0x0a, 0x0f, 0xbd, 0x71, 0x59, 0x7d, 0x87, 0x9d, + 0x24, 0x4a, 0xbb, 0xac, 0x5d, 0x18, 0x3d, 0x0e, 0x76, 0x1c, 0x01, 0x7e, 0xd5, 0x24, 0x6b, 0x47, + 0xfe, 0x67, 0xde, 0x18, 0x0e, 0xd6, 0xd8, 0xce, 0xb4, 0x18, 0x76, 0xed, 0x4d, 0x36, 0xe7, 0x66, + 0x5e, 0x93, 0x9b, 0x23, 0x8e, 0xa0, 0xab, 0x16, 0x56, 0x2f, 0x6c, 0x26, 0xe4, 0x94, 0x6e, 0xbc, + 0x81, 0x3d, 0xb6, 0x86, 0x8d, 0x8e, 0x60, 0xc0, 0x68, 0x26, 0x8c, 0xa5, 0x6a, 0xcc, 0x97, 0x25, + 0x49, 0xdb, 0xcc, 0x80, 0xd0, 0x9a, 0x14, 0x6a, 0xe2, 0x0d, 0xee, 0x32, 0x1f, 0x8f, 0x06, 0xd0, + 0xdf, 0x60, 0xeb, 0x62, 0x39, 0xfa, 0x1d, 0x01, 0xd6, 0xc0, 0xca, 0x52, 0xe1, 0x6b, 0xe8, 0xea, + 0x80, 0xde, 0xce, 0xd7, 0x65, 0x9d, 0x1a, 0xfb, 0xe4, 0x66, 0x7c, 0x07, 0x4f, 0x1a, 0x8a, 0xa1, + 0xbc, 0x22, 0xeb, 0xa7, 0xec, 0xb2, 0x5e, 0x8d, 0x7e, 0xf3, 0xa0, 0xdb, 0x96, 0x29, 0x19, 0x2b, + 0xa4, 0x17, 0xae, 0x67, 0x5a, 0x85, 0xf0, 0x04, 0xfa, 0x42, 0xe6, 0xaa, 0x14, 0x72, 0x96, 0xf1, + 0x52, 0x2d, 0xa4, 0x0d, 0x46, 0x86, 0xfd, 0xc3, 0x26, 0x77, 0xee, 0x53, 0x97, 0xce, 0xcd, 0x13, + 0xe8, 0xab, 0x85, 0x9d, 0xa9, 0xbb, 0x15, 0xdb, 0xa1, 0xa2, 0xc9, 0xdd, 0x56, 0x9c, 0xfe, 0x89, + 0xa0, 0x57, 0xdf, 0xbd, 0xb3, 0x81, 0x2a, 0xfc, 0x0e, 0xb8, 0xf9, 0x98, 0xf0, 0xd5, 0xb1, 0x7f, + 0xab, 0x0f, 0x3e, 0xce, 0xe1, 0xe1, 0xc3, 0x04, 0x77, 0x9d, 0x8f, 0xf0, 0x1c, 0x3a, 0x2b, 0x46, + 0x63, 0x1c, 0xf8, 0x9b, 0x9b, 0x32, 0x1c, 0xdc, 0x93, 0x09, 0x12, 0x97, 0xb0, 0x7f, 0xc7, 0x2b, + 0x7c, 0x19, 0xc8, 0xf7, 0x1b, 0x3e, 0x1c, 0x3e, 0x90, 0xf5, 0x72, 0x17, 0x6f, 0xa0, 0x2f, 0xd4, + 0xf1, 0xac, 0xd2, 0x79, 0xa0, 0x19, 0xaa, 0x7e, 0x8a, 0x9c, 0x2e, 0xda, 0x5f, 0x8c, 0x9e, 0x8e, + 0xdd, 0x5f, 0x69, 0x1c, 0x4d, 0x76, 0xfc, 0xef, 0xe9, 0xec, 0x6f, 0x00, 0x00, 0x00, 0xff, 0xff, + 0x41, 0x89, 0xc1, 0x2c, 0xac, 0x04, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. diff --git a/rpc/lspd.proto b/rpc/lspd.proto index e0792c5..44f7b2f 100644 --- a/rpc/lspd.proto +++ b/rpc/lspd.proto @@ -45,10 +45,9 @@ message ChannelInformationReply { /// the channel. int64 min_htlc_msat = 9 [ json_name = "min_htlc_msat" ]; - int64 channel_fee_start_amount = 10; - float channel_fee_rate = 11; + int64 channel_fee_permyriad = 10; - bytes lsp_pubkey = 12; + bytes lsp_pubkey = 11; } message OpenChannelRequest { diff --git a/server.go b/server.go index 868ae8d..08f41e5 100644 --- a/server.go +++ b/server.go @@ -29,15 +29,14 @@ import ( ) const ( - channelAmount = 1_000_000 - targetConf = 1 - minHtlcMsat = 600 - baseFeeMsat = 1000 - feeRate = 0.000001 - timeLockDelta = 144 - channelFeeStartAmount = 100_000 - channelFeeAmountNumerator = 1 - channelFeeAmountDenominator = 1000 + channelAmount = 1_000_000 + targetConf = 1 + minHtlcMsat = 600 + baseFeeMsat = 1000 + feeRate = 0.000001 + timeLockDelta = 144 + channelFeePermyriad = 10 + additionalChannelCapacity = 100_000 ) type server struct{} @@ -54,18 +53,17 @@ var ( 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: channelAmount, - TargetConf: targetConf, - MinHtlcMsat: minHtlcMsat, - BaseFeeMsat: baseFeeMsat, - FeeRate: feeRate, - TimeLockDelta: timeLockDelta, - ChannelFeeStartAmount: channelFeeStartAmount, - ChannelFeeRate: 1.0 * channelFeeAmountNumerator / channelFeeAmountDenominator, - LspPubkey: publicKey.SerializeCompressed(), + Name: nodeName, + Pubkey: nodePubkey, + Host: os.Getenv("NODE_HOST"), + ChannelCapacity: channelAmount, + TargetConf: targetConf, + MinHtlcMsat: minHtlcMsat, + BaseFeeMsat: baseFeeMsat, + FeeRate: feeRate, + TimeLockDelta: timeLockDelta, + ChannelFeePermyriad: channelFeePermyriad, + LspPubkey: publicKey.SerializeCompressed(), }, nil } From 34a38ba99c7a012f3ef1f41e1e24b29f7363157d Mon Sep 17 00:00:00 2001 From: Yaacov Akiba Slama Date: Thu, 1 Oct 2020 17:04:15 +0300 Subject: [PATCH 036/214] Decrease target conf to 6 --- intercept.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/intercept.go b/intercept.go index 53e960c..2c5bb1e 100644 --- a/intercept.go +++ b/intercept.go @@ -37,7 +37,7 @@ func openChannel(ctx context.Context, client lnrpc.LightningClient, paymentHash, channelPoint, err := client.OpenChannelSync(ctx, &lnrpc.OpenChannelRequest{ NodePubkey: destination, LocalFundingAmount: capacity, - TargetConf: 20, + TargetConf: 6, Private: true, }) if err != nil { From faff8f60a9226bc7570e534c50a022edb7e0c85a Mon Sep 17 00:00:00 2001 From: Yaacov Akiba Slama Date: Sun, 8 Nov 2020 17:34:09 +0200 Subject: [PATCH 037/214] Fix migration file name --- ...e_paymens_table.up.sql => 000001_create_payments_table.up.sql} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename postgresql/migrations/{000001_create_paymens_table.up.sql => 000001_create_payments_table.up.sql} (100%) diff --git a/postgresql/migrations/000001_create_paymens_table.up.sql b/postgresql/migrations/000001_create_payments_table.up.sql similarity index 100% rename from postgresql/migrations/000001_create_paymens_table.up.sql rename to postgresql/migrations/000001_create_payments_table.up.sql From f407ec9e9cb70d048e3d64157934694602e9c072 Mon Sep 17 00:00:00 2001 From: Yaacov Akiba Slama Date: Sun, 8 Nov 2020 17:46:26 +0200 Subject: [PATCH 038/214] Handle interception probing payments probing payments uses a probing payment hash which is: sha256("probing-01:" || payment_hash). When the interceptor detects such a hash for a payment which is supposed to trigger a channel creation , it checks if the destination is online, and if online, fails with INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS error in order to let the payer knows that the payment would be successful. --- db.go | 20 ++++----- go.mod | 8 ++-- intercept.go | 43 ++++++++++++++++--- ..._create_probe_payments_hash_index.down.sql | 1 + ...04_create_probe_payments_hash_index.up.sql | 1 + 5 files changed, 53 insertions(+), 20 deletions(-) create mode 100644 postgresql/migrations/000004_create_probe_payments_hash_index.down.sql create mode 100644 postgresql/migrations/000004_create_probe_payments_hash_index.up.sql diff --git a/db.go b/db.go index cdb5e36..eb2883c 100644 --- a/db.go +++ b/db.go @@ -24,25 +24,25 @@ func pgConnect() error { return nil } -func paymentInfo(paymentHash []byte) ([]byte, []byte, int64, int64, []byte, uint32, error) { +func paymentInfo(htlcPaymentHash []byte) ([]byte, []byte, []byte, int64, int64, []byte, uint32, error) { var ( - paymentSecret, destination []byte - incomingAmountMsat, outgoingAmountMsat int64 - fundingTxID []byte - fundingTxOutnum pgtype.Int4 + paymentHash, paymentSecret, destination []byte + incomingAmountMsat, outgoingAmountMsat int64 + fundingTxID []byte + fundingTxOutnum pgtype.Int4 ) err := pgxPool.QueryRow(context.Background(), - `SELECT payment_secret, destination, incoming_amount_msat, outgoing_amount_msat, funding_tx_id, funding_tx_outnum + `SELECT payment_hash, payment_secret, destination, incoming_amount_msat, outgoing_amount_msat, funding_tx_id, funding_tx_outnum FROM payments - WHERE payment_hash=$1`, - paymentHash).Scan(&paymentSecret, &destination, &incomingAmountMsat, &outgoingAmountMsat, &fundingTxID, &fundingTxOutnum) + WHERE payment_hash=$1 OR sha256('probing-01:' || payment_hash)=$1`, + htlcPaymentHash).Scan(&paymentHash, &paymentSecret, &destination, &incomingAmountMsat, &outgoingAmountMsat, &fundingTxID, &fundingTxOutnum) if err != nil { if err == pgx.ErrNoRows { err = nil } - return nil, nil, 0, 0, nil, 0, err + return nil, nil, nil, 0, 0, nil, 0, err } - return paymentSecret, destination, incomingAmountMsat, outgoingAmountMsat, fundingTxID, uint32(fundingTxOutnum.Int), nil + return paymentHash, paymentSecret, destination, incomingAmountMsat, outgoingAmountMsat, fundingTxID, uint32(fundingTxOutnum.Int), nil } func setFundingTx(paymentHash, fundingTxID []byte, fundingTxOutnum int) error { diff --git a/go.mod b/go.mod index a8ee50b..f791783 100644 --- a/go.mod +++ b/go.mod @@ -4,16 +4,16 @@ go 1.14 require ( github.com/aws/aws-sdk-go v1.30.20 - github.com/btcsuite/btcd v0.20.1-beta + github.com/btcsuite/btcd v0.20.1-beta.0.20200730232343-1db1b6f8217f github.com/caddyserver/certmagic v0.11.2 github.com/golang/protobuf v1.4.2 github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 github.com/jackc/pgtype v1.4.2 github.com/jackc/pgx/v4 v4.8.1 - github.com/lightningnetwork/lightning-onion v1.0.1 - github.com/lightningnetwork/lnd v0.10.0-beta + github.com/lightningnetwork/lightning-onion v1.0.2-0.20200501022730-3c8c8d0b89ea + github.com/lightningnetwork/lnd v0.11.0-beta golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e google.golang.org/grpc v1.31.0 ) -replace github.com/lightningnetwork/lnd v0.10.0-beta => github.com/breez/lnd v0.10.0-beta.rc6.0.20200727142715-f67a1052c0e0 +replace github.com/lightningnetwork/lnd v0.11.0-beta => github.com/breez/lnd v0.11.0-beta.rc4.0.20201101122458-227226f00b18 diff --git a/intercept.go b/intercept.go index 2c5bb1e..ab629d5 100644 --- a/intercept.go +++ b/intercept.go @@ -3,6 +3,7 @@ package main import ( "bytes" "context" + "encoding/hex" "fmt" "log" "math/big" @@ -32,6 +33,23 @@ func checkPayment(incomingAmountMsat, outgoingAmountMsat int64) error { return nil } +func isConnected(ctx context.Context, client lnrpc.LightningClient, destination []byte) error { + pubKey := hex.EncodeToString(destination) + r, err := client.ListPeers(ctx, &lnrpc.ListPeersRequest{LatestError: true}) + if err != nil { + log.Printf("client.ListPeers() error: %v", err) + return fmt.Errorf("client.ListPeers() error: %w", err) + } + for _, peer := range r.Peers { + if pubKey == peer.PubKey { + log.Printf("destination online: %x", destination) + return nil + } + } + log.Printf("destination offline: %x", destination) + return fmt.Errorf("destination offline") +} + func openChannel(ctx context.Context, client lnrpc.LightningClient, paymentHash, destination []byte, incomingAmountMsat int64) ([]byte, uint32, error) { capacity := incomingAmountMsat/1000 + additionalChannelCapacity channelPoint, err := client.OpenChannelSync(ctx, &lnrpc.OpenChannelRequest{ @@ -104,21 +122,34 @@ func intercept() { request.OnionBlob, ) - paymentSecret, destination, incomingAmountMsat, outgoingAmountMsat, fundingTxID, fundingTxOutnum, err := paymentInfo(request.PaymentHash) + paymentHash, paymentSecret, destination, incomingAmountMsat, outgoingAmountMsat, fundingTxID, fundingTxOutnum, err := paymentInfo(request.PaymentHash) if err != nil { log.Printf("paymentInfo(%x) error: %v", request.PaymentHash, err) } - log.Printf("paymentSecret: %x\ndestination:%x\nincomingAmountMsat:%v\noutgoingAmountMsat:%v\n\n", - paymentSecret, destination, incomingAmountMsat, outgoingAmountMsat) + log.Printf("paymentHash:%x\npaymentSecret:%x\ndestination:%x\nincomingAmountMsat:%v\noutgoingAmountMsat:%v\n\n", + paymentHash, paymentSecret, destination, incomingAmountMsat, outgoingAmountMsat) if paymentSecret != nil { if fundingTxID == nil { - fundingTxID, fundingTxOutnum, err = openChannel(clientCtx, client, request.PaymentHash, destination, incomingAmountMsat) - log.Printf("openChannel(%x, %v) err: %v", destination, incomingAmountMsat, err) - if err != nil { + if bytes.Compare(paymentHash, request.PaymentHash) == 0 { + fundingTxID, fundingTxOutnum, err = openChannel(clientCtx, client, request.PaymentHash, destination, incomingAmountMsat) + log.Printf("openChannel(%x, %v) err: %v", destination, incomingAmountMsat, err) + if err != nil { + interceptorClient.Send(&routerrpc.ForwardHtlcInterceptResponse{ + IncomingCircuitKey: request.IncomingCircuitKey, + Action: routerrpc.ResolveHoldForwardAction_FAIL, + }) + continue + } + } else { //probing + failureCode := routerrpc.ForwardHtlcInterceptResponse_TEMPORARY_CHANNEL_FAILURE + if err := isConnected(clientCtx, client, destination); err == nil { + failureCode = routerrpc.ForwardHtlcInterceptResponse_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS + } interceptorClient.Send(&routerrpc.ForwardHtlcInterceptResponse{ IncomingCircuitKey: request.IncomingCircuitKey, Action: routerrpc.ResolveHoldForwardAction_FAIL, + FailureCode: failureCode, }) continue } diff --git a/postgresql/migrations/000004_create_probe_payments_hash_index.down.sql b/postgresql/migrations/000004_create_probe_payments_hash_index.down.sql new file mode 100644 index 0000000..394ca43 --- /dev/null +++ b/postgresql/migrations/000004_create_probe_payments_hash_index.down.sql @@ -0,0 +1 @@ +DROP INDEX probe_payment_hash; \ No newline at end of file diff --git a/postgresql/migrations/000004_create_probe_payments_hash_index.up.sql b/postgresql/migrations/000004_create_probe_payments_hash_index.up.sql new file mode 100644 index 0000000..cef78e3 --- /dev/null +++ b/postgresql/migrations/000004_create_probe_payments_hash_index.up.sql @@ -0,0 +1 @@ +CREATE INDEX probe_payment_hash ON public.payments (sha256('probing-01:' || payment_hash)); \ No newline at end of file From aa0db5402cd667e5150bcd1c957622080f77541d Mon Sep 17 00:00:00 2001 From: Yaacov Akiba Slama Date: Mon, 16 Nov 2020 12:14:37 +0200 Subject: [PATCH 039/214] Add dynamic channel creation documentation --- README.md | 25 +++++++++++++------------ rpc/lspd.md | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index bc66a66..16fe219 100644 --- a/README.md +++ b/README.md @@ -5,18 +5,11 @@ This is a simple example of an lspd that works with an [lnd](https://github.com/ ## Installation 1. git clone https://github.com/breez/lspd (or fork) -2. Modify the code in server.go if you use different values than the recommeded values when opening channels: - * **ChannelCapacity**: channel capacity is sats, defined in the channelAmount const (recommended: 1000000). - * **TargetConf**: the number of blocks that the funding transaction *should* confirm in, will be used for fee estimation (recommended: 0). - * **MinHtlcMsat**: the channel_reserve value in sats (recommended: 1000000). - * **BaseFeeMsat**: base tx fee in msats (recommended: 1000). - * **FeeRate**: fee rate (recommended: 0.000001). The total fee charged is BaseFeeMsat + (amount * FeeRate / 1000000) - * **TimeLockDelta**: the minimum number of blocks this node requires to be added to the expiry of HTLCs (recommended: 144). -3. Compile lspd using `go build .` -4. Create a random token (for instance using the command `openssl rand -base64 48`) -5. Define the environment variables as described in sample.env. If `CERTMAGIC_DOMAIN` is defined, certificate for this domain is automatically obtained and renewed from Let's Encrypt. In this case, the port needs to be 443. If `CERTMAGIC_DOMAIN` is not defined, lspd needs to run behind a reverse proxy like treafik or nginx. -6. Run lspd -7. Share with Breez the TOKEN and the LISTEN_ADDRESS you've defined (send to contact@breez.technology) +1. Compile lspd using `go build .` +1. Create a random token (for instance using the command `openssl rand -base64 48`) +1. Define the environment variables as described in sample.env. If `CERTMAGIC_DOMAIN` is defined, certificate for this domain is automatically obtained and renewed from Let's Encrypt. In this case, the port needs to be 443. If `CERTMAGIC_DOMAIN` is not defined, lspd needs to run behind a reverse proxy like treafik or nginx. +1. Run lspd +1. Share with Breez the TOKEN and the LISTEN_ADDRESS you've defined (send to contact@breez.technology) ## Implement your own lspd You can create your own lsdp by implementing the grpc methods described [here](https://github.com/breez/lspd/blob/master/rpc/lspd.md). @@ -24,3 +17,11 @@ You can create your own lsdp by implementing the grpc methods described [here](h ## Use a smaller channel reserve You can apply the PR from https://github.com/lightningnetwork/lnd/pull/2708 to be able to create channels with a channel reserve smaller than 1% of the channel capacity. Then add the field `RemoteChanReserveSat` in the `lnrpc.OpenChannelRequest` struct when opening a channel. + +## Flow for creating channels +When Alice wants Bob to pay her an amount and Alice doesn't have a channel with sufficient capacity, she calls the lspd function RegisterPayment() and sending the paymentHash, paymentSecret (for mpp payments), destination (Alice pubkey), and two amounts. +The first amount (incoming from the lsp point of view) is the amount BOB will pay. The second amount (outgoing from the lsp point of view) is the amount Alice will receive. The difference between these two amounts is the fees for the lsp. +In order to open the channel on the fly, the lsp is connecting to lnd using the interceptor api. + +## Probing support +The lsp supports probing non-mpp payments if the payment hash for probing is sha256('probing-01:' || payment_hash) when payment_hash is the hash of the real payment. diff --git a/rpc/lspd.md b/rpc/lspd.md index 2f86135..6e3b38f 100644 --- a/rpc/lspd.md +++ b/rpc/lspd.md @@ -8,6 +8,9 @@ - [ChannelInformationRequest](#lspd.ChannelInformationRequest) - [OpenChannelReply](#lspd.OpenChannelReply) - [OpenChannelRequest](#lspd.OpenChannelRequest) + - [PaymentInformation](#lspd.PaymentInformation) + - [RegisterPaymentReply](#lspd.RegisterPaymentReply) + - [RegisterPaymentRequest](#lspd.RegisterPaymentRequest) @@ -42,6 +45,8 @@ | fee_rate | [double](#double) | | The effective fee rate in milli-satoshis. The precision of this value goes / up to 6 decimal places, so 1e-6. | | time_lock_delta | [uint32](#uint32) | | The required timelock delta for HTLCs forwarded over the channel. | | min_htlc_msat | [int64](#int64) | | The minimum value in millisatoshi we will require for incoming HTLCs on / the channel. | +| channel_fee_permyriad | [int64](#int64) | | | +| lsp_pubkey | [bytes](#bytes) | | | @@ -93,6 +98,50 @@ + +
+ +### PaymentInformation + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| payment_hash | [bytes](#bytes) | | | +| payment_secret | [bytes](#bytes) | | | +| destination | [bytes](#bytes) | | | +| incoming_amount_msat | [int64](#int64) | | | +| outgoing_amount_msat | [int64](#int64) | | | + + + + + + + + +### RegisterPaymentReply + + + + + + + + + +### RegisterPaymentRequest + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| blob | [bytes](#bytes) | | | + + + + + @@ -109,6 +158,7 @@ | ----------- | ------------ | ------------- | ------------| | ChannelInformation | [ChannelInformationRequest](#lspd.ChannelInformationRequest) | [ChannelInformationReply](#lspd.ChannelInformationReply) | | | OpenChannel | [OpenChannelRequest](#lspd.OpenChannelRequest) | [OpenChannelReply](#lspd.OpenChannelReply) | | +| RegisterPayment | [RegisterPaymentRequest](#lspd.RegisterPaymentRequest) | [RegisterPaymentReply](#lspd.RegisterPaymentReply) | | From 11dc17ae2da47a5547529c92d746c11f7e6f15c0 Mon Sep 17 00:00:00 2001 From: Yaacov Akiba Slama Date: Thu, 24 Dec 2020 18:01:57 +0200 Subject: [PATCH 040/214] Add checkChannels grpc function --- email.go | 42 +++++++ rpc/lspd.pb.go | 331 +++++++++++++++++++++++++++++++++++++++++++------ rpc/lspd.proto | 22 ++++ sample.env | 6 +- server.go | 135 ++++++++++++++++++++ 5 files changed, 498 insertions(+), 38 deletions(-) diff --git a/email.go b/email.go index 3277a48..0cccb39 100644 --- a/email.go +++ b/email.go @@ -82,6 +82,48 @@ func sendEmail(to, cc, from, content, subject string) error { return nil } +func sendChannelMismatchNotification(nodeID string, notFakeChannels, closedChannels map[string]uint64) error { + var html bytes.Buffer + + tpl := ` +

NodeID: {{ .NodeID }}

+ {{ if .NotFakeChannels }}

Channels not fake anynmore

+ + {{ range $key, $value := .NotFakeChannels }}{{ end }} +
Channel PointHeight Hint
{{ $key }}{{ $value }}
{{ end }} + {{ if .ClosedChannels }}

Closed Channels

+ + {{ range $key, $value := .ClosedChannels }}{{ end }} +
Channel PointHeight Hint
{{ $key }}{{ $value }}
{{ end }} + ` + t, err := template.New("ChannelMismatchEmail").Parse(tpl) + if err != nil { + return err + } + + if err := t.Execute(&html, struct { + NodeID string + NotFakeChannels map[string]uint64 + ClosedChannels map[string]uint64 + }{nodeID, notFakeChannels, closedChannels}); err != nil { + return err + } + + err = sendEmail( + os.Getenv("CHANNELMISMATCH_NOTIFICATION_TO"), + os.Getenv("CHANNELMISMATCH_NOTIFICATION_CC"), + os.Getenv("CHANNELMISMATCH_NOTIFICATION_FROM"), + html.String(), + "Channel(s) Mismatch", + ) + if err != nil { + log.Printf("Error sending open channel email: %v", err) + return err + } + + return nil +} + func sendOpenChannelEmailNotification( paymentHash []byte, incomingAmountMsat int64, destination []byte, capacity int64, diff --git a/rpc/lspd.pb.go b/rpc/lspd.pb.go index fe84bee..714a501 100644 --- a/rpc/lspd.pb.go +++ b/rpc/lspd.pb.go @@ -426,6 +426,202 @@ func (m *PaymentInformation) GetOutgoingAmountMsat() int64 { return 0 } +type Encrypted struct { + Data []byte `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Encrypted) Reset() { *m = Encrypted{} } +func (m *Encrypted) String() string { return proto.CompactTextString(m) } +func (*Encrypted) ProtoMessage() {} +func (*Encrypted) Descriptor() ([]byte, []int) { + return fileDescriptor_c69a0f5a734bca26, []int{7} +} + +func (m *Encrypted) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Encrypted.Unmarshal(m, b) +} +func (m *Encrypted) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Encrypted.Marshal(b, m, deterministic) +} +func (m *Encrypted) XXX_Merge(src proto.Message) { + xxx_messageInfo_Encrypted.Merge(m, src) +} +func (m *Encrypted) XXX_Size() int { + return xxx_messageInfo_Encrypted.Size(m) +} +func (m *Encrypted) XXX_DiscardUnknown() { + xxx_messageInfo_Encrypted.DiscardUnknown(m) +} + +var xxx_messageInfo_Encrypted proto.InternalMessageInfo + +func (m *Encrypted) GetData() []byte { + if m != nil { + return m.Data + } + return nil +} + +type Signed struct { + Data []byte `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"` + Pubkey []byte `protobuf:"bytes,2,opt,name=pubkey,proto3" json:"pubkey,omitempty"` + Signature []byte `protobuf:"bytes,3,opt,name=signature,proto3" json:"signature,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Signed) Reset() { *m = Signed{} } +func (m *Signed) String() string { return proto.CompactTextString(m) } +func (*Signed) ProtoMessage() {} +func (*Signed) Descriptor() ([]byte, []int) { + return fileDescriptor_c69a0f5a734bca26, []int{8} +} + +func (m *Signed) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Signed.Unmarshal(m, b) +} +func (m *Signed) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Signed.Marshal(b, m, deterministic) +} +func (m *Signed) XXX_Merge(src proto.Message) { + xxx_messageInfo_Signed.Merge(m, src) +} +func (m *Signed) XXX_Size() int { + return xxx_messageInfo_Signed.Size(m) +} +func (m *Signed) XXX_DiscardUnknown() { + xxx_messageInfo_Signed.DiscardUnknown(m) +} + +var xxx_messageInfo_Signed proto.InternalMessageInfo + +func (m *Signed) GetData() []byte { + if m != nil { + return m.Data + } + return nil +} + +func (m *Signed) GetPubkey() []byte { + if m != nil { + return m.Pubkey + } + return nil +} + +func (m *Signed) GetSignature() []byte { + if m != nil { + return m.Signature + } + return nil +} + +type CheckChannelsRequest struct { + EncryptPubkey []byte `protobuf:"bytes,1,opt,name=encrypt_pubkey,json=encryptPubkey,proto3" json:"encrypt_pubkey,omitempty"` + FakeChannels map[string]uint64 `protobuf:"bytes,2,rep,name=fake_channels,json=fakeChannels,proto3" json:"fake_channels,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"varint,2,opt,name=value,proto3"` + WaitingCloseChannels map[string]uint64 `protobuf:"bytes,3,rep,name=waiting_close_channels,json=waitingCloseChannels,proto3" json:"waiting_close_channels,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"varint,2,opt,name=value,proto3"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *CheckChannelsRequest) Reset() { *m = CheckChannelsRequest{} } +func (m *CheckChannelsRequest) String() string { return proto.CompactTextString(m) } +func (*CheckChannelsRequest) ProtoMessage() {} +func (*CheckChannelsRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_c69a0f5a734bca26, []int{9} +} + +func (m *CheckChannelsRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_CheckChannelsRequest.Unmarshal(m, b) +} +func (m *CheckChannelsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_CheckChannelsRequest.Marshal(b, m, deterministic) +} +func (m *CheckChannelsRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_CheckChannelsRequest.Merge(m, src) +} +func (m *CheckChannelsRequest) XXX_Size() int { + return xxx_messageInfo_CheckChannelsRequest.Size(m) +} +func (m *CheckChannelsRequest) XXX_DiscardUnknown() { + xxx_messageInfo_CheckChannelsRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_CheckChannelsRequest proto.InternalMessageInfo + +func (m *CheckChannelsRequest) GetEncryptPubkey() []byte { + if m != nil { + return m.EncryptPubkey + } + return nil +} + +func (m *CheckChannelsRequest) GetFakeChannels() map[string]uint64 { + if m != nil { + return m.FakeChannels + } + return nil +} + +func (m *CheckChannelsRequest) GetWaitingCloseChannels() map[string]uint64 { + if m != nil { + return m.WaitingCloseChannels + } + return nil +} + +type CheckChannelsReply struct { + NotFakeChannels map[string]uint64 `protobuf:"bytes,1,rep,name=not_fake_channels,json=notFakeChannels,proto3" json:"not_fake_channels,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"varint,2,opt,name=value,proto3"` + ClosedChannels map[string]uint64 `protobuf:"bytes,2,rep,name=closed_channels,json=closedChannels,proto3" json:"closed_channels,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"varint,2,opt,name=value,proto3"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *CheckChannelsReply) Reset() { *m = CheckChannelsReply{} } +func (m *CheckChannelsReply) String() string { return proto.CompactTextString(m) } +func (*CheckChannelsReply) ProtoMessage() {} +func (*CheckChannelsReply) Descriptor() ([]byte, []int) { + return fileDescriptor_c69a0f5a734bca26, []int{10} +} + +func (m *CheckChannelsReply) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_CheckChannelsReply.Unmarshal(m, b) +} +func (m *CheckChannelsReply) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_CheckChannelsReply.Marshal(b, m, deterministic) +} +func (m *CheckChannelsReply) XXX_Merge(src proto.Message) { + xxx_messageInfo_CheckChannelsReply.Merge(m, src) +} +func (m *CheckChannelsReply) XXX_Size() int { + return xxx_messageInfo_CheckChannelsReply.Size(m) +} +func (m *CheckChannelsReply) XXX_DiscardUnknown() { + xxx_messageInfo_CheckChannelsReply.DiscardUnknown(m) +} + +var xxx_messageInfo_CheckChannelsReply proto.InternalMessageInfo + +func (m *CheckChannelsReply) GetNotFakeChannels() map[string]uint64 { + if m != nil { + return m.NotFakeChannels + } + return nil +} + +func (m *CheckChannelsReply) GetClosedChannels() map[string]uint64 { + if m != nil { + return m.ClosedChannels + } + return nil +} + func init() { proto.RegisterType((*ChannelInformationRequest)(nil), "lspd.ChannelInformationRequest") proto.RegisterType((*ChannelInformationReply)(nil), "lspd.ChannelInformationReply") @@ -434,6 +630,14 @@ func init() { proto.RegisterType((*RegisterPaymentRequest)(nil), "lspd.RegisterPaymentRequest") proto.RegisterType((*RegisterPaymentReply)(nil), "lspd.RegisterPaymentReply") proto.RegisterType((*PaymentInformation)(nil), "lspd.PaymentInformation") + proto.RegisterType((*Encrypted)(nil), "lspd.Encrypted") + proto.RegisterType((*Signed)(nil), "lspd.Signed") + proto.RegisterType((*CheckChannelsRequest)(nil), "lspd.CheckChannelsRequest") + proto.RegisterMapType((map[string]uint64)(nil), "lspd.CheckChannelsRequest.FakeChannelsEntry") + proto.RegisterMapType((map[string]uint64)(nil), "lspd.CheckChannelsRequest.WaitingCloseChannelsEntry") + proto.RegisterType((*CheckChannelsReply)(nil), "lspd.CheckChannelsReply") + proto.RegisterMapType((map[string]uint64)(nil), "lspd.CheckChannelsReply.ClosedChannelsEntry") + proto.RegisterMapType((map[string]uint64)(nil), "lspd.CheckChannelsReply.NotFakeChannelsEntry") } func init() { @@ -441,43 +645,60 @@ func init() { } var fileDescriptor_c69a0f5a734bca26 = []byte{ - // 568 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x84, 0x54, 0xff, 0x6e, 0xd3, 0x30, - 0x10, 0x26, 0xac, 0xfb, 0xd1, 0x6b, 0xcb, 0xa6, 0xa3, 0x94, 0x50, 0x31, 0x11, 0x0a, 0x48, 0x11, - 0x9a, 0xa6, 0x69, 0x7b, 0x82, 0x0d, 0x09, 0x81, 0xc4, 0x44, 0x65, 0x24, 0xfe, 0x8d, 0xdc, 0xf4, - 0xd6, 0x5a, 0x4b, 0x6c, 0x13, 0xbb, 0x68, 0x7d, 0x07, 0x1e, 0x93, 0x97, 0xe0, 0x3f, 0x64, 0x3b, - 0x61, 0xed, 0xba, 0x89, 0xff, 0xce, 0xdf, 0x7d, 0xf7, 0xdd, 0xc5, 0xdf, 0x39, 0x00, 0x85, 0xd1, - 0xd3, 0x63, 0x5d, 0x29, 0xab, 0xb0, 0xe5, 0xe2, 0xd1, 0x19, 0xbc, 0xf8, 0x30, 0xe7, 0x52, 0x52, - 0xf1, 0x59, 0x5e, 0xa9, 0xaa, 0xe4, 0x56, 0x28, 0xc9, 0xe8, 0xc7, 0x82, 0x8c, 0xc5, 0x01, 0xec, - 0xe8, 0xc5, 0xe4, 0x9a, 0x96, 0x71, 0x94, 0x44, 0x69, 0x9b, 0xd5, 0xa7, 0xd1, 0xaf, 0x2d, 0x78, - 0x7e, 0x5f, 0x95, 0x2e, 0x96, 0x88, 0xd0, 0x92, 0xbc, 0xa4, 0xba, 0xc2, 0xc7, 0x2b, 0x3a, 0x8f, - 0x57, 0x75, 0x1c, 0x77, 0xae, 0x8c, 0x8d, 0xb7, 0x02, 0xd7, 0xc5, 0xf8, 0x1e, 0x0e, 0xf2, 0x20, - 0x9d, 0xe5, 0x5c, 0xf3, 0x5c, 0xd8, 0x65, 0xdc, 0x4a, 0xa2, 0x74, 0x8b, 0x6d, 0xe0, 0x98, 0x40, - 0xc7, 0xf2, 0x6a, 0x46, 0x36, 0xcb, 0x95, 0xbc, 0x8a, 0xb7, 0x93, 0x28, 0xdd, 0x66, 0xab, 0x10, - 0xbe, 0x85, 0xde, 0x84, 0x1b, 0xca, 0xae, 0x88, 0xb2, 0xd2, 0x70, 0x1b, 0xef, 0x78, 0xa9, 0x75, - 0x10, 0x87, 0xb0, 0xe7, 0xe2, 0x8a, 0x5b, 0x8a, 0x77, 0x93, 0x28, 0x8d, 0xd8, 0xbf, 0x33, 0xa6, - 0xb0, 0x6f, 0x45, 0x49, 0x59, 0xa1, 0xf2, 0xeb, 0x6c, 0x4a, 0x85, 0xe5, 0xf1, 0x5e, 0x12, 0xa5, - 0x3d, 0x76, 0x17, 0x76, 0xbd, 0x4a, 0x21, 0xb3, 0xb9, 0x2d, 0xf2, 0xd0, 0xab, 0x1d, 0x7a, 0xad, - 0x81, 0x78, 0x0a, 0xcf, 0x9a, 0xef, 0x70, 0x3d, 0x34, 0x55, 0xe5, 0xb2, 0x12, 0x7c, 0x1a, 0x83, - 0x67, 0x3f, 0xad, 0x93, 0x1f, 0x89, 0xc6, 0x4d, 0x0a, 0x0f, 0xbd, 0x71, 0x59, 0x7d, 0x87, 0x9d, - 0x24, 0x4a, 0xbb, 0xac, 0x5d, 0x18, 0x3d, 0x0e, 0x76, 0x1c, 0x01, 0x7e, 0xd5, 0x24, 0x6b, 0x47, - 0xfe, 0x67, 0xde, 0x18, 0x0e, 0xd6, 0xd8, 0xce, 0xb4, 0x18, 0x76, 0xed, 0x4d, 0x36, 0xe7, 0x66, - 0x5e, 0x93, 0x9b, 0x23, 0x8e, 0xa0, 0xab, 0x16, 0x56, 0x2f, 0x6c, 0x26, 0xe4, 0x94, 0x6e, 0xbc, - 0x81, 0x3d, 0xb6, 0x86, 0x8d, 0x8e, 0x60, 0xc0, 0x68, 0x26, 0x8c, 0xa5, 0x6a, 0xcc, 0x97, 0x25, - 0x49, 0xdb, 0xcc, 0x80, 0xd0, 0x9a, 0x14, 0x6a, 0xe2, 0x0d, 0xee, 0x32, 0x1f, 0x8f, 0x06, 0xd0, - 0xdf, 0x60, 0xeb, 0x62, 0x39, 0xfa, 0x1d, 0x01, 0xd6, 0xc0, 0xca, 0x52, 0xe1, 0x6b, 0xe8, 0xea, - 0x80, 0xde, 0xce, 0xd7, 0x65, 0x9d, 0x1a, 0xfb, 0xe4, 0x66, 0x7c, 0x07, 0x4f, 0x1a, 0x8a, 0xa1, - 0xbc, 0x22, 0xeb, 0xa7, 0xec, 0xb2, 0x5e, 0x8d, 0x7e, 0xf3, 0xa0, 0xdb, 0x96, 0x29, 0x19, 0x2b, - 0xa4, 0x17, 0xae, 0x67, 0x5a, 0x85, 0xf0, 0x04, 0xfa, 0x42, 0xe6, 0xaa, 0x14, 0x72, 0x96, 0xf1, - 0x52, 0x2d, 0xa4, 0x0d, 0x46, 0x86, 0xfd, 0xc3, 0x26, 0x77, 0xee, 0x53, 0x97, 0xce, 0xcd, 0x13, - 0xe8, 0xab, 0x85, 0x9d, 0xa9, 0xbb, 0x15, 0xdb, 0xa1, 0xa2, 0xc9, 0xdd, 0x56, 0x9c, 0xfe, 0x89, - 0xa0, 0x57, 0xdf, 0xbd, 0xb3, 0x81, 0x2a, 0xfc, 0x0e, 0xb8, 0xf9, 0x98, 0xf0, 0xd5, 0xb1, 0x7f, - 0xab, 0x0f, 0x3e, 0xce, 0xe1, 0xe1, 0xc3, 0x04, 0x77, 0x9d, 0x8f, 0xf0, 0x1c, 0x3a, 0x2b, 0x46, - 0x63, 0x1c, 0xf8, 0x9b, 0x9b, 0x32, 0x1c, 0xdc, 0x93, 0x09, 0x12, 0x97, 0xb0, 0x7f, 0xc7, 0x2b, - 0x7c, 0x19, 0xc8, 0xf7, 0x1b, 0x3e, 0x1c, 0x3e, 0x90, 0xf5, 0x72, 0x17, 0x6f, 0xa0, 0x2f, 0xd4, - 0xf1, 0xac, 0xd2, 0x79, 0xa0, 0x19, 0xaa, 0x7e, 0x8a, 0x9c, 0x2e, 0xda, 0x5f, 0x8c, 0x9e, 0x8e, - 0xdd, 0x5f, 0x69, 0x1c, 0x4d, 0x76, 0xfc, 0xef, 0xe9, 0xec, 0x6f, 0x00, 0x00, 0x00, 0xff, 0xff, - 0x41, 0x89, 0xc1, 0x2c, 0xac, 0x04, 0x00, 0x00, + // 846 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x56, 0xef, 0x8e, 0xdb, 0x44, + 0x10, 0xaf, 0x93, 0xdc, 0xb5, 0x99, 0x38, 0x97, 0xeb, 0x36, 0x04, 0xd7, 0x6a, 0xd5, 0x60, 0xa8, + 0x14, 0xa1, 0x23, 0xaa, 0xee, 0xf8, 0x80, 0xf8, 0x82, 0xee, 0x4e, 0x2d, 0x20, 0x51, 0x08, 0xae, + 0x00, 0xf1, 0xc9, 0xda, 0xd8, 0x93, 0xc4, 0xc4, 0xde, 0x35, 0xf6, 0xba, 0x3d, 0xbf, 0x00, 0x9f, + 0x78, 0x16, 0xde, 0x84, 0xb7, 0xe0, 0x41, 0xd0, 0xee, 0xda, 0x77, 0x76, 0xe2, 0x83, 0xde, 0xb7, + 0xd9, 0xdf, 0xcc, 0xfc, 0xe6, 0xdf, 0xee, 0xd8, 0x00, 0x51, 0x96, 0x04, 0xf3, 0x24, 0xe5, 0x82, + 0x93, 0x9e, 0x94, 0x9d, 0x33, 0x78, 0x7c, 0xb9, 0xa1, 0x8c, 0x61, 0xf4, 0x2d, 0x5b, 0xf1, 0x34, + 0xa6, 0x22, 0xe4, 0xcc, 0xc5, 0xdf, 0x73, 0xcc, 0x04, 0x99, 0xc0, 0x61, 0x92, 0x2f, 0xb7, 0x58, + 0x58, 0xc6, 0xd4, 0x98, 0xf5, 0xdd, 0xf2, 0xe4, 0xfc, 0xd9, 0x85, 0x0f, 0xdb, 0xbc, 0x92, 0xa8, + 0x20, 0x04, 0x7a, 0x8c, 0xc6, 0x58, 0x7a, 0x28, 0xb9, 0xc6, 0xd3, 0xa9, 0xf3, 0x48, 0xdb, 0x0d, + 0xcf, 0x84, 0xd5, 0xd5, 0xb6, 0x52, 0x26, 0x9f, 0xc2, 0xb1, 0xaf, 0xa9, 0x3d, 0x9f, 0x26, 0xd4, + 0x0f, 0x45, 0x61, 0xf5, 0xa6, 0xc6, 0xac, 0xeb, 0xee, 0xe1, 0x64, 0x0a, 0x03, 0x41, 0xd3, 0x35, + 0x0a, 0xcf, 0xe7, 0x6c, 0x65, 0x1d, 0x4c, 0x8d, 0xd9, 0x81, 0x5b, 0x87, 0xc8, 0x27, 0x30, 0x5c, + 0xd2, 0x0c, 0xbd, 0x15, 0xa2, 0x17, 0x67, 0x54, 0x58, 0x87, 0x8a, 0xaa, 0x09, 0x12, 0x1b, 0x1e, + 0x48, 0x39, 0xa5, 0x02, 0xad, 0xfb, 0x53, 0x63, 0x66, 0xb8, 0xd7, 0x67, 0x32, 0x83, 0x91, 0x08, + 0x63, 0xf4, 0x22, 0xee, 0x6f, 0xbd, 0x00, 0x23, 0x41, 0xad, 0x07, 0x53, 0x63, 0x36, 0x74, 0x77, + 0x61, 0x19, 0x2b, 0x0e, 0x99, 0xb7, 0x11, 0x91, 0xaf, 0x63, 0xf5, 0x75, 0xac, 0x06, 0x48, 0x4e, + 0xe1, 0x83, 0xaa, 0x0e, 0x19, 0x23, 0xc1, 0x34, 0x2e, 0xd2, 0x90, 0x06, 0x16, 0x28, 0xeb, 0x47, + 0xa5, 0xf2, 0x15, 0xe2, 0xa2, 0x52, 0x91, 0xa7, 0x6a, 0x70, 0x5e, 0xd9, 0xc3, 0xc1, 0xd4, 0x98, + 0x99, 0x6e, 0x3f, 0xca, 0x92, 0x85, 0x1e, 0xc7, 0x09, 0x90, 0x1f, 0x12, 0x64, 0xe5, 0x44, 0xfe, + 0x6f, 0x78, 0x0b, 0x38, 0x6e, 0x58, 0xcb, 0xa1, 0x59, 0x70, 0x5f, 0x5c, 0x79, 0x1b, 0x9a, 0x6d, + 0x4a, 0xe3, 0xea, 0x48, 0x1c, 0x30, 0x79, 0x2e, 0x92, 0x5c, 0x78, 0x21, 0x0b, 0xf0, 0x4a, 0x0d, + 0x70, 0xe8, 0x36, 0x30, 0xe7, 0x04, 0x26, 0x2e, 0xae, 0xc3, 0x4c, 0x60, 0xba, 0xa0, 0x45, 0x8c, + 0x4c, 0x54, 0x39, 0x10, 0xe8, 0x2d, 0x23, 0xbe, 0x54, 0x03, 0x36, 0x5d, 0x25, 0x3b, 0x13, 0x18, + 0xef, 0x59, 0x27, 0x51, 0xe1, 0xfc, 0x63, 0x00, 0x29, 0x81, 0xda, 0xa5, 0x22, 0x1f, 0x81, 0x99, + 0x68, 0xf4, 0x26, 0x3f, 0xd3, 0x1d, 0x94, 0xd8, 0x37, 0x32, 0xc7, 0xe7, 0x70, 0x54, 0x99, 0x64, + 0xe8, 0xa7, 0x28, 0x54, 0x96, 0xa6, 0x3b, 0x2c, 0xd1, 0x37, 0x0a, 0x94, 0xb7, 0x25, 0xc0, 0x4c, + 0x84, 0x4c, 0x11, 0x97, 0x39, 0xd5, 0x21, 0xf2, 0x02, 0xc6, 0x21, 0xf3, 0x79, 0x1c, 0xb2, 0xb5, + 0x47, 0x63, 0x9e, 0x33, 0xa1, 0x07, 0xa9, 0xef, 0x1f, 0xa9, 0x74, 0xe7, 0x4a, 0xf5, 0x5a, 0x4e, + 0xf3, 0x05, 0x8c, 0x79, 0x2e, 0xd6, 0x7c, 0xd7, 0xe3, 0x40, 0x7b, 0x54, 0xba, 0x1b, 0x0f, 0xe7, + 0x19, 0xf4, 0x5f, 0x32, 0x3f, 0x2d, 0x12, 0x81, 0x81, 0xec, 0x4f, 0x40, 0x05, 0x2d, 0x8b, 0x52, + 0xb2, 0xe3, 0xc2, 0xe1, 0x9b, 0x70, 0xcd, 0xda, 0xb5, 0x3b, 0x4f, 0xc9, 0xbc, 0x7e, 0x4a, 0x4f, + 0xa0, 0x9f, 0x85, 0x6b, 0x46, 0x45, 0x9e, 0x62, 0x59, 0xda, 0x0d, 0xe0, 0xfc, 0xd1, 0x85, 0xf1, + 0xe5, 0x06, 0xfd, 0x6d, 0x39, 0xf5, 0xac, 0x1a, 0xd0, 0x73, 0x38, 0x42, 0x9d, 0x8d, 0x57, 0xbb, + 0x2c, 0xa6, 0x3b, 0x2c, 0x51, 0x7d, 0xc3, 0xc8, 0x8f, 0x30, 0x5c, 0xd1, 0x2d, 0x7a, 0xe5, 0xe5, + 0xcc, 0xac, 0xce, 0xb4, 0x3b, 0x1b, 0x9c, 0x9e, 0xcc, 0xd5, 0x3e, 0x69, 0x63, 0x9e, 0xbf, 0xa2, + 0x5b, 0xac, 0xb0, 0x97, 0x4c, 0xa4, 0x85, 0x6b, 0xae, 0x6a, 0x10, 0xf9, 0x0d, 0x26, 0xef, 0x68, + 0x28, 0x64, 0xe3, 0xfc, 0x88, 0x67, 0x35, 0xee, 0xae, 0xe2, 0xfe, 0xfc, 0x3f, 0xb8, 0x7f, 0xd1, + 0x8e, 0x97, 0xd2, 0xaf, 0x19, 0x63, 0xfc, 0xae, 0x45, 0x65, 0x7f, 0x05, 0x0f, 0xf7, 0xd2, 0x21, + 0xc7, 0xd0, 0xbd, 0x79, 0x1c, 0x52, 0x24, 0x63, 0x38, 0x78, 0x4b, 0xa3, 0x1c, 0x55, 0x6b, 0x7b, + 0xae, 0x3e, 0x7c, 0xd9, 0xf9, 0xc2, 0xb0, 0xbf, 0x86, 0xc7, 0xb7, 0xc6, 0xbc, 0x0b, 0x91, 0xf3, + 0x77, 0x07, 0xc8, 0x4e, 0x49, 0xf2, 0xfd, 0xfd, 0x0a, 0x0f, 0x19, 0x17, 0x5e, 0xb3, 0xc7, 0x86, + 0xea, 0xc3, 0x67, 0xad, 0x7d, 0x48, 0xa2, 0x62, 0xfe, 0x3d, 0x17, 0xfb, 0x4d, 0x1e, 0xb1, 0x26, + 0x4a, 0x7e, 0x82, 0x91, 0xea, 0x6f, 0xf0, 0x7e, 0xc3, 0x93, 0xc4, 0xaa, 0xc6, 0xa0, 0xc9, 0x7b, + 0xe4, 0x37, 0x40, 0xfb, 0x02, 0xc6, 0x6d, 0xf1, 0xef, 0xd4, 0xd5, 0x73, 0x78, 0xd4, 0x12, 0xea, + 0x2e, 0x14, 0xa7, 0x7f, 0x75, 0x60, 0x58, 0x7a, 0xcb, 0xa5, 0x86, 0x29, 0xf9, 0x59, 0x36, 0x78, + 0xf7, 0xd3, 0x44, 0x9e, 0x55, 0xc5, 0xde, 0xf2, 0xa9, 0xb3, 0x9f, 0xde, 0x6e, 0x20, 0x97, 0xd3, + 0x3d, 0x72, 0x0e, 0x83, 0xda, 0xda, 0x24, 0x96, 0xb6, 0xdf, 0xdf, 0xbb, 0xf6, 0xa4, 0x45, 0xa3, + 0x29, 0x5e, 0xc3, 0x68, 0x67, 0xf3, 0x91, 0x27, 0xda, 0xb8, 0x7d, 0x7d, 0xda, 0xf6, 0x2d, 0x5a, + 0x4d, 0x77, 0x26, 0x4b, 0xaf, 0x0d, 0x8f, 0x8c, 0xb4, 0xf9, 0xf5, 0x7a, 0xb1, 0x77, 0x01, 0xe7, + 0xde, 0xc5, 0xc7, 0x30, 0x0e, 0xf9, 0x7c, 0x9d, 0x26, 0xbe, 0xd6, 0x65, 0x98, 0xbe, 0x0d, 0x7d, + 0xbc, 0xe8, 0x7f, 0x97, 0x25, 0xc1, 0x42, 0xfe, 0x18, 0x2c, 0x8c, 0xe5, 0xa1, 0xfa, 0x43, 0x38, + 0xfb, 0x37, 0x00, 0x00, 0xff, 0xff, 0x05, 0x11, 0xba, 0x22, 0x2f, 0x08, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -495,6 +716,7 @@ type ChannelOpenerClient interface { ChannelInformation(ctx context.Context, in *ChannelInformationRequest, opts ...grpc.CallOption) (*ChannelInformationReply, error) OpenChannel(ctx context.Context, in *OpenChannelRequest, opts ...grpc.CallOption) (*OpenChannelReply, error) RegisterPayment(ctx context.Context, in *RegisterPaymentRequest, opts ...grpc.CallOption) (*RegisterPaymentReply, error) + CheckChannels(ctx context.Context, in *Encrypted, opts ...grpc.CallOption) (*Encrypted, error) } type channelOpenerClient struct { @@ -532,11 +754,21 @@ func (c *channelOpenerClient) RegisterPayment(ctx context.Context, in *RegisterP return out, nil } +func (c *channelOpenerClient) CheckChannels(ctx context.Context, in *Encrypted, opts ...grpc.CallOption) (*Encrypted, error) { + out := new(Encrypted) + err := c.cc.Invoke(ctx, "/lspd.ChannelOpener/CheckChannels", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // ChannelOpenerServer is the server API for ChannelOpener service. type ChannelOpenerServer interface { ChannelInformation(context.Context, *ChannelInformationRequest) (*ChannelInformationReply, error) OpenChannel(context.Context, *OpenChannelRequest) (*OpenChannelReply, error) RegisterPayment(context.Context, *RegisterPaymentRequest) (*RegisterPaymentReply, error) + CheckChannels(context.Context, *Encrypted) (*Encrypted, error) } // UnimplementedChannelOpenerServer can be embedded to have forward compatible implementations. @@ -552,6 +784,9 @@ func (*UnimplementedChannelOpenerServer) OpenChannel(ctx context.Context, req *O func (*UnimplementedChannelOpenerServer) RegisterPayment(ctx context.Context, req *RegisterPaymentRequest) (*RegisterPaymentReply, error) { return nil, status.Errorf(codes.Unimplemented, "method RegisterPayment not implemented") } +func (*UnimplementedChannelOpenerServer) CheckChannels(ctx context.Context, req *Encrypted) (*Encrypted, error) { + return nil, status.Errorf(codes.Unimplemented, "method CheckChannels not implemented") +} func RegisterChannelOpenerServer(s *grpc.Server, srv ChannelOpenerServer) { s.RegisterService(&_ChannelOpener_serviceDesc, srv) @@ -611,6 +846,24 @@ func _ChannelOpener_RegisterPayment_Handler(srv interface{}, ctx context.Context return interceptor(ctx, in, info, handler) } +func _ChannelOpener_CheckChannels_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Encrypted) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ChannelOpenerServer).CheckChannels(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/lspd.ChannelOpener/CheckChannels", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ChannelOpenerServer).CheckChannels(ctx, req.(*Encrypted)) + } + return interceptor(ctx, in, info, handler) +} + var _ChannelOpener_serviceDesc = grpc.ServiceDesc{ ServiceName: "lspd.ChannelOpener", HandlerType: (*ChannelOpenerServer)(nil), @@ -627,6 +880,10 @@ var _ChannelOpener_serviceDesc = grpc.ServiceDesc{ MethodName: "RegisterPayment", Handler: _ChannelOpener_RegisterPayment_Handler, }, + { + MethodName: "CheckChannels", + Handler: _ChannelOpener_CheckChannels_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "lspd.proto", diff --git a/rpc/lspd.proto b/rpc/lspd.proto index 44f7b2f..a1b8308 100644 --- a/rpc/lspd.proto +++ b/rpc/lspd.proto @@ -11,6 +11,7 @@ service ChannelOpener { returns (ChannelInformationReply) {} rpc OpenChannel(OpenChannelRequest) returns (OpenChannelReply) {} rpc RegisterPayment (RegisterPaymentRequest) returns (RegisterPaymentReply) {} + rpc CheckChannels(Encrypted) returns (Encrypted) {} } message ChannelInformationRequest { @@ -73,3 +74,24 @@ message PaymentInformation { int64 incoming_amount_msat = 4; int64 outgoing_amount_msat = 5; } + +message Encrypted { + bytes data = 1; +} + +message Signed { + bytes data = 1; + bytes pubkey = 2; + bytes signature = 3; +} + +message CheckChannelsRequest { + bytes encrypt_pubkey = 1; + map fake_channels = 2; + map waiting_close_channels = 3; +} + +message CheckChannelsReply { + map not_fake_channels = 1; + map closed_channels = 2; +} \ No newline at end of file diff --git a/sample.env b/sample.env index ee103b9..880c770 100644 --- a/sample.env +++ b/sample.env @@ -21,4 +21,8 @@ AWS_SECRET_ACCESS_KEY= OPENCHANNEL_NOTIFICATION_TO='["Name1 "]' OPENCHANNEL_NOTIFICATION_CC='["Name2 ","Name3 "]' -OPENCHANNEL_NOTIFICATION_FROM="Name4 " \ No newline at end of file +OPENCHANNEL_NOTIFICATION_FROM="Name4 " + +CHANNELMISMATCH_NOTIFICATION_TO='["Name1 "]' +CHANNELMISMATCH_NOTIFICATION_CC='["Name2 ","Name3 "]' +CHANNELMISMATCH_NOTIFICATION_FROM="Name4 " \ No newline at end of file diff --git a/server.go b/server.go index 08f41e5..f96cac1 100644 --- a/server.go +++ b/server.go @@ -20,6 +20,7 @@ import ( grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware" "github.com/lightningnetwork/lnd/lnrpc" "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" @@ -140,6 +141,140 @@ func (s *server) OpenChannel(ctx context.Context, in *lspdrpc.OpenChannelRequest return r.(*lspdrpc.OpenChannelReply), err } +func getSignedEncryptedData(in *lspdrpc.Encrypted) (string, []byte, error) { + signedBlob, err := btcec.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, btcec.S256()) + if err != nil { + log.Printf("unable to parse pubkey: %w", 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) + } + if len(notFakeChannels) != 0 || len(closedChannels) != 0 { + err = sendChannelMismatchNotification(nodeID, notFakeChannels, closedChannels) + if err != nil { + log.Printf("sendChannelMismatchNotification() error: %v", 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, btcec.S256()) + if err != nil { + log.Printf("unable to parse pubkey: %v", err) + return nil, fmt.Errorf("unable to parse pubkey: %w", err) + } + encrypted, err := btcec.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) { + var r map[string]uint64 + channels, err := getNodeChannels(nodeID) + if err != nil { + return nil, err + } + for _, c := range channels { + if h, ok := channelPoints[c.ChannelPoint]; ok { + sid := lnwire.NewShortChanIDFromInt(c.ChanId) + if !sid.IsFake() { + r[c.ChannelPoint] = h + } + } + } + return r, nil +} + +func getClosedChannels(nodeID string, channelPoints map[string]uint64) (map[string]uint64, error) { + var r map[string]uint64 + 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{}) From ea54442821aeab4130b51076ab23ee63fbe5dabf Mon Sep 17 00:00:00 2001 From: Yaacov Akiba Slama Date: Tue, 29 Dec 2020 16:32:21 +0200 Subject: [PATCH 041/214] Fix typo in logging --- server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server.go b/server.go index f96cac1..86f2ff7 100644 --- a/server.go +++ b/server.go @@ -155,7 +155,7 @@ func getSignedEncryptedData(in *lspdrpc.Encrypted) (string, []byte, error) { } pubkey, err := btcec.ParsePubKey(signed.Pubkey, btcec.S256()) if err != nil { - log.Printf("unable to parse pubkey: %w", err) + log.Printf("unable to parse pubkey: %v", err) return "", nil, fmt.Errorf("unable to parse pubkey: %w", err) } wireSig, err := lnwire.NewSigFromRawSignature(signed.Signature) From 97fdf045e4dc34a0e712e4015df43872207135f5 Mon Sep 17 00:00:00 2001 From: Yaacov Akiba Slama Date: Wed, 30 Dec 2020 17:00:08 +0200 Subject: [PATCH 042/214] Initialize the the map before using it --- server.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server.go b/server.go index 86f2ff7..223f308 100644 --- a/server.go +++ b/server.go @@ -226,7 +226,7 @@ func (s *server) CheckChannels(ctx context.Context, in *lspdrpc.Encrypted) (*lsp } func getNotFakeChannels(nodeID string, channelPoints map[string]uint64) (map[string]uint64, error) { - var r map[string]uint64 + r := make(map[string]uint64) channels, err := getNodeChannels(nodeID) if err != nil { return nil, err @@ -243,7 +243,7 @@ func getNotFakeChannels(nodeID string, channelPoints map[string]uint64) (map[str } func getClosedChannels(nodeID string, channelPoints map[string]uint64) (map[string]uint64, error) { - var r map[string]uint64 + r := make(map[string]uint64) waitingCloseChannels, err := getWaitingCloseChannels(nodeID) if err != nil { return nil, err From 22b2b365edf1af955b30224aaaf340f70b3e70ae Mon Sep 17 00:00:00 2001 From: Yaacov Akiba Slama Date: Sun, 3 Jan 2021 18:49:44 +0200 Subject: [PATCH 043/214] Returns the short channel id if the channel is already confirmed --- server.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server.go b/server.go index 223f308..9c37c8d 100644 --- a/server.go +++ b/server.go @@ -232,10 +232,10 @@ func getNotFakeChannels(nodeID string, channelPoints map[string]uint64) (map[str return nil, err } for _, c := range channels { - if h, ok := channelPoints[c.ChannelPoint]; ok { + if _, ok := channelPoints[c.ChannelPoint]; ok { sid := lnwire.NewShortChanIDFromInt(c.ChanId) if !sid.IsFake() { - r[c.ChannelPoint] = h + r[c.ChannelPoint] = c.ChanId } } } From aca8c4f2c0cf709a1a0829fc48e4233d3674310e Mon Sep 17 00:00:00 2001 From: Yaacov Akiba Slama Date: Fri, 5 Feb 2021 07:11:53 +0200 Subject: [PATCH 044/214] Add forwarding history sync from lnd to db --- db.go | 62 ++++++++++++++ forwarding_history.go | 85 +++++++++++++++++++ go.mod | 2 +- intercept.go | 4 + .../000005_forwarding_history.down.sql | 1 + .../000005_forwarding_history.up.sql | 10 +++ .../migrations/000006_channels.down.sql | 1 + postgresql/migrations/000006_channels.up.sql | 7 ++ server.go | 2 + 9 files changed, 173 insertions(+), 1 deletion(-) create mode 100644 forwarding_history.go create mode 100644 postgresql/migrations/000005_forwarding_history.down.sql create mode 100644 postgresql/migrations/000005_forwarding_history.up.sql create mode 100644 postgresql/migrations/000006_channels.down.sql create mode 100644 postgresql/migrations/000006_channels.up.sql diff --git a/db.go b/db.go index eb2883c..b082f41 100644 --- a/db.go +++ b/db.go @@ -69,3 +69,65 @@ func registerPayment(destination, paymentHash, paymentSecret []byte, incomingAmo } return nil } + +func insertChannel(chanID uint64, channelPoint string, nodeID []byte) error { + _, err := pgxPool.Exec(context.Background(), + `INSERT INTO + channels (chanis, channel_point, nodeid) + VALUES ($1, $2, $3)`, + chanID, channelPoint, nodeID) + if err != nil { + return fmt.Errorf("insertChannel(%v, %s, %x) error: %w", + chanID, channelPoint, nodeID, err) + } + return nil +} + +func lastForwardingEvent() (int64, error) { + var last int64 + err := pgxPool.QueryRow(context.Background(), + `SELECT coalesce(MAX("timestamp"), 0) AS last FROM forwarding_history`).Scan(&last) + if err != nil { + return 0, err + } + return last, nil +} + +func insertForwardingEvents(rowSrc pgx.CopyFromSource) error { + + tx, err := pgxPool.Begin(context.Background()) + if err != nil { + return fmt.Errorf("pgxPool.Begin() error: %w", err) + } + defer tx.Rollback(context.Background()) + + _, err = tx.Exec(context.Background(), ` + CREATE TEMP TABLE tmp_table ON COMMIT DROP AS + SELECT * + FROM forwarding_history + WITH NO DATA; + `) + if err != nil { + return fmt.Errorf("CREATE TEMP TABLE error: %w", err) + } + + count, err := tx.CopyFrom(context.Background(), + pgx.Identifier{"tmp_table"}, + []string{"timestamp", "chanid_in", "chanid_out", "amt_msat_in", "amt_msat_out"}, rowSrc) + if err != nil { + return fmt.Errorf("CopyFrom() error: %w", err) + } + log.Printf("count1: %v", count) + + cmdTag, err := tx.Exec(context.Background(), ` + INSERT INTO forwarding_history + SELECT * + FROM tmp_table + ON CONFLICT DO NOTHING + `) + if err != nil { + return fmt.Errorf("INSERT INTO forwarding_history error: %w", err) + } + log.Printf("count2: %v", cmdTag.RowsAffected()) + return tx.Commit(context.Background()) +} diff --git a/forwarding_history.go b/forwarding_history.go new file mode 100644 index 0000000..5ee298f --- /dev/null +++ b/forwarding_history.go @@ -0,0 +1,85 @@ +package main + +import ( + "context" + "fmt" + "log" + "os" + "time" + + "github.com/lightningnetwork/lnd/lnrpc" + "google.golang.org/grpc/metadata" +) + +type copyFromEvents struct { + events []*lnrpc.ForwardingEvent + idx int + err error +} + +func (cfe *copyFromEvents) Next() bool { + cfe.idx++ + return cfe.idx < len(cfe.events) +} + +func (cfe *copyFromEvents) Values() ([]interface{}, error) { + event := cfe.events[cfe.idx] + values := []interface{}{ + event.TimestampNs, + event.ChanIdIn, event.ChanIdOut, + event.AmtInMsat, event.AmtOutMsat} + return values, nil +} + +func (cfe *copyFromEvents) Err() error { + return cfe.err +} + +func forwardingHistorySynchronize() { + for { + err := forwardingHistorySynchronizeOnce() + log.Printf("forwardingHistorySynchronizeOnce() err: %v", err) + time.Sleep(1 * time.Minute) + } +} + +func forwardingHistorySynchronizeOnce() error { + last, err := lastForwardingEvent() + if err != nil { + return fmt.Errorf("lastForwardingEvent() error: %w", err) + } + log.Printf("last1: %v", last) + last = last/1_000_000_000 - 1*3600 + if last <= 0 { + last = 1 + } + log.Printf("last2: %v", last) + now := time.Now() + endTime := uint64(now.Add(time.Hour * 24).Unix()) + clientCtx := metadata.AppendToOutgoingContext(context.Background(), "macaroon", os.Getenv("LND_MACAROON_HEX")) + indexOffset := uint32(0) + for { + forwardHistory, err := client.ForwardingHistory(clientCtx, &lnrpc.ForwardingHistoryRequest{ + StartTime: uint64(last), + EndTime: endTime, + NumMaxEvents: 10000, + IndexOffset: indexOffset, + }) + if err != nil { + log.Printf("ForwardingHistory error: %v", err) + return fmt.Errorf("client.ForwardingHistory() error: %w", err) + } + log.Printf("Offset: %v, Events: %v", indexOffset, len(forwardHistory.ForwardingEvents)) + if len(forwardHistory.ForwardingEvents) == 0 { + break + } + indexOffset = forwardHistory.LastOffsetIndex + cfe := copyFromEvents{events: forwardHistory.ForwardingEvents, idx: -1} + err = insertForwardingEvents(&cfe) + if err != nil { + log.Printf("insertForwardingEvents() error: %v", err) + return fmt.Errorf("insertForwardingEvents() error: %w", err) + } + } + return nil +} diff --git a/go.mod b/go.mod index f791783..92414a7 100644 --- a/go.mod +++ b/go.mod @@ -16,4 +16,4 @@ require ( google.golang.org/grpc v1.31.0 ) -replace github.com/lightningnetwork/lnd v0.11.0-beta => github.com/breez/lnd v0.11.0-beta.rc4.0.20201101122458-227226f00b18 +replace github.com/lightningnetwork/lnd v0.11.0-beta => github.com/breez/lnd v0.11.0-beta.rc4.0.20210125150416-0c10146b223c diff --git a/intercept.go b/intercept.go index ab629d5..68bdb80 100644 --- a/intercept.go +++ b/intercept.go @@ -240,6 +240,10 @@ func resumeOrCancel( OutgoingRequestedChanId: chanID, OnionBlob: onionBlob, }) + err := insertChannel(chanID, channelPoint, destination) + if err != nil { + log.Printf("insertChannel error: %v", err) + } return } log.Printf("getChannel(%x, %v) returns 0", destination, channelPoint) diff --git a/postgresql/migrations/000005_forwarding_history.down.sql b/postgresql/migrations/000005_forwarding_history.down.sql new file mode 100644 index 0000000..4dcb607 --- /dev/null +++ b/postgresql/migrations/000005_forwarding_history.down.sql @@ -0,0 +1 @@ +DROP TABLE public.forwarding_history; \ No newline at end of file diff --git a/postgresql/migrations/000005_forwarding_history.up.sql b/postgresql/migrations/000005_forwarding_history.up.sql new file mode 100644 index 0000000..0762b1a --- /dev/null +++ b/postgresql/migrations/000005_forwarding_history.up.sql @@ -0,0 +1,10 @@ +CREATE TABLE public.forwarding_history ( + "timestamp" bigint NOT NULL, + chanid_in bigint NOT NULL, + chanid_out bigint NOT NULL, + amt_msat_in bigint NOT NULL, + amt_msat_out bigint NOT NULL, + CONSTRAINT timestamp_pkey PRIMARY KEY ("timestamp") +); +CREATE INDEX forwarding_history_chanid_in_idx ON public.forwarding_history (chanid_in); +CREATE INDEX forwarding_history_chanid_out_idx ON public.forwarding_history (chanid_out); \ No newline at end of file diff --git a/postgresql/migrations/000006_channels.down.sql b/postgresql/migrations/000006_channels.down.sql new file mode 100644 index 0000000..14a4bcc --- /dev/null +++ b/postgresql/migrations/000006_channels.down.sql @@ -0,0 +1 @@ +DROP TABLE public.channels; \ No newline at end of file diff --git a/postgresql/migrations/000006_channels.up.sql b/postgresql/migrations/000006_channels.up.sql new file mode 100644 index 0000000..659a565 --- /dev/null +++ b/postgresql/migrations/000006_channels.up.sql @@ -0,0 +1,7 @@ +CREATE TABLE public.channels ( + chanid bigint NOT NULL, + channel_point varchar NULL, + nodeid bytea NULL, + CONSTRAINT chanid_pkey PRIMARY KEY (chanid) +); +CREATE INDEX channels_nodeid_idx ON public.channels (nodeid); diff --git a/server.go b/server.go index 9c37c8d..74aaf48 100644 --- a/server.go +++ b/server.go @@ -376,6 +376,8 @@ func main() { go intercept() + go forwardingHistorySynchronize() + 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 { From f3a66cc6fbae09ac74c3367d06ecd3f873cc63fd Mon Sep 17 00:00:00 2001 From: Yaacov Akiba Slama Date: Fri, 5 Feb 2021 10:10:10 +0200 Subject: [PATCH 045/214] Fix typo in the field name --- db.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db.go b/db.go index b082f41..f28f883 100644 --- a/db.go +++ b/db.go @@ -73,7 +73,7 @@ func registerPayment(destination, paymentHash, paymentSecret []byte, incomingAmo func insertChannel(chanID uint64, channelPoint string, nodeID []byte) error { _, err := pgxPool.Exec(context.Background(), `INSERT INTO - channels (chanis, channel_point, nodeid) + channels (chanid, channel_point, nodeid) VALUES ($1, $2, $3)`, chanID, channelPoint, nodeID) if err != nil { From ece77f65e1160fd687ee11a620cb171aebd317e4 Mon Sep 17 00:00:00 2001 From: Yaacov Akiba Slama Date: Thu, 11 Feb 2021 17:13:15 +0200 Subject: [PATCH 046/214] Downgrade google.golang.org/grpc to 1.29.1 --- go.mod | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 92414a7..7573a3a 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,9 @@ require ( github.com/aws/aws-sdk-go v1.30.20 github.com/btcsuite/btcd v0.20.1-beta.0.20200730232343-1db1b6f8217f github.com/caddyserver/certmagic v0.11.2 + github.com/coreos/etcd v3.3.25+incompatible // indirect + github.com/coreos/go-semver v0.3.0 // indirect + github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f // indirect github.com/golang/protobuf v1.4.2 github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 github.com/jackc/pgtype v1.4.2 @@ -13,7 +16,7 @@ require ( github.com/lightningnetwork/lightning-onion v1.0.2-0.20200501022730-3c8c8d0b89ea github.com/lightningnetwork/lnd v0.11.0-beta golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e - google.golang.org/grpc v1.31.0 + google.golang.org/grpc v1.29.1 ) replace github.com/lightningnetwork/lnd v0.11.0-beta => github.com/breez/lnd v0.11.0-beta.rc4.0.20210125150416-0c10146b223c From d532a78143d0dfe013e4c061562ebf5afe06866f Mon Sep 17 00:00:00 2001 From: Yaacov Akiba Slama Date: Sun, 21 Feb 2021 17:19:02 +0200 Subject: [PATCH 047/214] Add new field maxInactiveDuration --- rpc/lspd.pb.go | 125 +++++++++++++++++++++++++++---------------------- rpc/lspd.proto | 3 ++ server.go | 2 + 3 files changed, 73 insertions(+), 57 deletions(-) diff --git a/rpc/lspd.pb.go b/rpc/lspd.pb.go index 714a501..e3262f3 100644 --- a/rpc/lspd.pb.go +++ b/rpc/lspd.pb.go @@ -86,9 +86,11 @@ type ChannelInformationReply struct { TimeLockDelta uint32 `protobuf:"varint,8,opt,name=time_lock_delta,proto3" json:"time_lock_delta,omitempty"` /// The minimum value in millisatoshi we will require for incoming HTLCs on /// the channel. - MinHtlcMsat int64 `protobuf:"varint,9,opt,name=min_htlc_msat,proto3" json:"min_htlc_msat,omitempty"` - ChannelFeePermyriad int64 `protobuf:"varint,10,opt,name=channel_fee_permyriad,json=channelFeePermyriad,proto3" json:"channel_fee_permyriad,omitempty"` - LspPubkey []byte `protobuf:"bytes,11,opt,name=lsp_pubkey,json=lspPubkey,proto3" json:"lsp_pubkey,omitempty"` + MinHtlcMsat int64 `protobuf:"varint,9,opt,name=min_htlc_msat,proto3" json:"min_htlc_msat,omitempty"` + ChannelFeePermyriad int64 `protobuf:"varint,10,opt,name=channel_fee_permyriad,json=channelFeePermyriad,proto3" json:"channel_fee_permyriad,omitempty"` + LspPubkey []byte `protobuf:"bytes,11,opt,name=lsp_pubkey,json=lspPubkey,proto3" json:"lsp_pubkey,omitempty"` + // The channel can be closed if not used this duration in seconds. + MaxInactiveDuration int64 `protobuf:"varint,12,opt,name=max_inactive_duration,json=maxInactiveDuration,proto3" json:"max_inactive_duration,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` @@ -196,6 +198,13 @@ func (m *ChannelInformationReply) GetLspPubkey() []byte { return nil } +func (m *ChannelInformationReply) GetMaxInactiveDuration() int64 { + if m != nil { + return m.MaxInactiveDuration + } + return 0 +} + type OpenChannelRequest struct { /// The identity pubkey of the Lightning node Pubkey string `protobuf:"bytes,1,opt,name=pubkey,proto3" json:"pubkey,omitempty"` @@ -645,60 +654,62 @@ func init() { } var fileDescriptor_c69a0f5a734bca26 = []byte{ - // 846 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x56, 0xef, 0x8e, 0xdb, 0x44, - 0x10, 0xaf, 0x93, 0xdc, 0xb5, 0x99, 0x38, 0x97, 0xeb, 0x36, 0x04, 0xd7, 0x6a, 0xd5, 0x60, 0xa8, - 0x14, 0xa1, 0x23, 0xaa, 0xee, 0xf8, 0x80, 0xf8, 0x82, 0xee, 0x4e, 0x2d, 0x20, 0x51, 0x08, 0xae, - 0x00, 0xf1, 0xc9, 0xda, 0xd8, 0x93, 0xc4, 0xc4, 0xde, 0x35, 0xf6, 0xba, 0x3d, 0xbf, 0x00, 0x9f, - 0x78, 0x16, 0xde, 0x84, 0xb7, 0xe0, 0x41, 0xd0, 0xee, 0xda, 0x77, 0x76, 0xe2, 0x83, 0xde, 0xb7, - 0xd9, 0xdf, 0xcc, 0xfc, 0xe6, 0xdf, 0xee, 0xd8, 0x00, 0x51, 0x96, 0x04, 0xf3, 0x24, 0xe5, 0x82, - 0x93, 0x9e, 0x94, 0x9d, 0x33, 0x78, 0x7c, 0xb9, 0xa1, 0x8c, 0x61, 0xf4, 0x2d, 0x5b, 0xf1, 0x34, - 0xa6, 0x22, 0xe4, 0xcc, 0xc5, 0xdf, 0x73, 0xcc, 0x04, 0x99, 0xc0, 0x61, 0x92, 0x2f, 0xb7, 0x58, - 0x58, 0xc6, 0xd4, 0x98, 0xf5, 0xdd, 0xf2, 0xe4, 0xfc, 0xd9, 0x85, 0x0f, 0xdb, 0xbc, 0x92, 0xa8, - 0x20, 0x04, 0x7a, 0x8c, 0xc6, 0x58, 0x7a, 0x28, 0xb9, 0xc6, 0xd3, 0xa9, 0xf3, 0x48, 0xdb, 0x0d, - 0xcf, 0x84, 0xd5, 0xd5, 0xb6, 0x52, 0x26, 0x9f, 0xc2, 0xb1, 0xaf, 0xa9, 0x3d, 0x9f, 0x26, 0xd4, - 0x0f, 0x45, 0x61, 0xf5, 0xa6, 0xc6, 0xac, 0xeb, 0xee, 0xe1, 0x64, 0x0a, 0x03, 0x41, 0xd3, 0x35, - 0x0a, 0xcf, 0xe7, 0x6c, 0x65, 0x1d, 0x4c, 0x8d, 0xd9, 0x81, 0x5b, 0x87, 0xc8, 0x27, 0x30, 0x5c, - 0xd2, 0x0c, 0xbd, 0x15, 0xa2, 0x17, 0x67, 0x54, 0x58, 0x87, 0x8a, 0xaa, 0x09, 0x12, 0x1b, 0x1e, - 0x48, 0x39, 0xa5, 0x02, 0xad, 0xfb, 0x53, 0x63, 0x66, 0xb8, 0xd7, 0x67, 0x32, 0x83, 0x91, 0x08, - 0x63, 0xf4, 0x22, 0xee, 0x6f, 0xbd, 0x00, 0x23, 0x41, 0xad, 0x07, 0x53, 0x63, 0x36, 0x74, 0x77, - 0x61, 0x19, 0x2b, 0x0e, 0x99, 0xb7, 0x11, 0x91, 0xaf, 0x63, 0xf5, 0x75, 0xac, 0x06, 0x48, 0x4e, - 0xe1, 0x83, 0xaa, 0x0e, 0x19, 0x23, 0xc1, 0x34, 0x2e, 0xd2, 0x90, 0x06, 0x16, 0x28, 0xeb, 0x47, - 0xa5, 0xf2, 0x15, 0xe2, 0xa2, 0x52, 0x91, 0xa7, 0x6a, 0x70, 0x5e, 0xd9, 0xc3, 0xc1, 0xd4, 0x98, - 0x99, 0x6e, 0x3f, 0xca, 0x92, 0x85, 0x1e, 0xc7, 0x09, 0x90, 0x1f, 0x12, 0x64, 0xe5, 0x44, 0xfe, - 0x6f, 0x78, 0x0b, 0x38, 0x6e, 0x58, 0xcb, 0xa1, 0x59, 0x70, 0x5f, 0x5c, 0x79, 0x1b, 0x9a, 0x6d, - 0x4a, 0xe3, 0xea, 0x48, 0x1c, 0x30, 0x79, 0x2e, 0x92, 0x5c, 0x78, 0x21, 0x0b, 0xf0, 0x4a, 0x0d, - 0x70, 0xe8, 0x36, 0x30, 0xe7, 0x04, 0x26, 0x2e, 0xae, 0xc3, 0x4c, 0x60, 0xba, 0xa0, 0x45, 0x8c, - 0x4c, 0x54, 0x39, 0x10, 0xe8, 0x2d, 0x23, 0xbe, 0x54, 0x03, 0x36, 0x5d, 0x25, 0x3b, 0x13, 0x18, - 0xef, 0x59, 0x27, 0x51, 0xe1, 0xfc, 0x63, 0x00, 0x29, 0x81, 0xda, 0xa5, 0x22, 0x1f, 0x81, 0x99, - 0x68, 0xf4, 0x26, 0x3f, 0xd3, 0x1d, 0x94, 0xd8, 0x37, 0x32, 0xc7, 0xe7, 0x70, 0x54, 0x99, 0x64, - 0xe8, 0xa7, 0x28, 0x54, 0x96, 0xa6, 0x3b, 0x2c, 0xd1, 0x37, 0x0a, 0x94, 0xb7, 0x25, 0xc0, 0x4c, - 0x84, 0x4c, 0x11, 0x97, 0x39, 0xd5, 0x21, 0xf2, 0x02, 0xc6, 0x21, 0xf3, 0x79, 0x1c, 0xb2, 0xb5, - 0x47, 0x63, 0x9e, 0x33, 0xa1, 0x07, 0xa9, 0xef, 0x1f, 0xa9, 0x74, 0xe7, 0x4a, 0xf5, 0x5a, 0x4e, - 0xf3, 0x05, 0x8c, 0x79, 0x2e, 0xd6, 0x7c, 0xd7, 0xe3, 0x40, 0x7b, 0x54, 0xba, 0x1b, 0x0f, 0xe7, - 0x19, 0xf4, 0x5f, 0x32, 0x3f, 0x2d, 0x12, 0x81, 0x81, 0xec, 0x4f, 0x40, 0x05, 0x2d, 0x8b, 0x52, - 0xb2, 0xe3, 0xc2, 0xe1, 0x9b, 0x70, 0xcd, 0xda, 0xb5, 0x3b, 0x4f, 0xc9, 0xbc, 0x7e, 0x4a, 0x4f, - 0xa0, 0x9f, 0x85, 0x6b, 0x46, 0x45, 0x9e, 0x62, 0x59, 0xda, 0x0d, 0xe0, 0xfc, 0xd1, 0x85, 0xf1, - 0xe5, 0x06, 0xfd, 0x6d, 0x39, 0xf5, 0xac, 0x1a, 0xd0, 0x73, 0x38, 0x42, 0x9d, 0x8d, 0x57, 0xbb, - 0x2c, 0xa6, 0x3b, 0x2c, 0x51, 0x7d, 0xc3, 0xc8, 0x8f, 0x30, 0x5c, 0xd1, 0x2d, 0x7a, 0xe5, 0xe5, - 0xcc, 0xac, 0xce, 0xb4, 0x3b, 0x1b, 0x9c, 0x9e, 0xcc, 0xd5, 0x3e, 0x69, 0x63, 0x9e, 0xbf, 0xa2, - 0x5b, 0xac, 0xb0, 0x97, 0x4c, 0xa4, 0x85, 0x6b, 0xae, 0x6a, 0x10, 0xf9, 0x0d, 0x26, 0xef, 0x68, - 0x28, 0x64, 0xe3, 0xfc, 0x88, 0x67, 0x35, 0xee, 0xae, 0xe2, 0xfe, 0xfc, 0x3f, 0xb8, 0x7f, 0xd1, - 0x8e, 0x97, 0xd2, 0xaf, 0x19, 0x63, 0xfc, 0xae, 0x45, 0x65, 0x7f, 0x05, 0x0f, 0xf7, 0xd2, 0x21, - 0xc7, 0xd0, 0xbd, 0x79, 0x1c, 0x52, 0x24, 0x63, 0x38, 0x78, 0x4b, 0xa3, 0x1c, 0x55, 0x6b, 0x7b, - 0xae, 0x3e, 0x7c, 0xd9, 0xf9, 0xc2, 0xb0, 0xbf, 0x86, 0xc7, 0xb7, 0xc6, 0xbc, 0x0b, 0x91, 0xf3, - 0x77, 0x07, 0xc8, 0x4e, 0x49, 0xf2, 0xfd, 0xfd, 0x0a, 0x0f, 0x19, 0x17, 0x5e, 0xb3, 0xc7, 0x86, - 0xea, 0xc3, 0x67, 0xad, 0x7d, 0x48, 0xa2, 0x62, 0xfe, 0x3d, 0x17, 0xfb, 0x4d, 0x1e, 0xb1, 0x26, - 0x4a, 0x7e, 0x82, 0x91, 0xea, 0x6f, 0xf0, 0x7e, 0xc3, 0x93, 0xc4, 0xaa, 0xc6, 0xa0, 0xc9, 0x7b, - 0xe4, 0x37, 0x40, 0xfb, 0x02, 0xc6, 0x6d, 0xf1, 0xef, 0xd4, 0xd5, 0x73, 0x78, 0xd4, 0x12, 0xea, - 0x2e, 0x14, 0xa7, 0x7f, 0x75, 0x60, 0x58, 0x7a, 0xcb, 0xa5, 0x86, 0x29, 0xf9, 0x59, 0x36, 0x78, - 0xf7, 0xd3, 0x44, 0x9e, 0x55, 0xc5, 0xde, 0xf2, 0xa9, 0xb3, 0x9f, 0xde, 0x6e, 0x20, 0x97, 0xd3, - 0x3d, 0x72, 0x0e, 0x83, 0xda, 0xda, 0x24, 0x96, 0xb6, 0xdf, 0xdf, 0xbb, 0xf6, 0xa4, 0x45, 0xa3, - 0x29, 0x5e, 0xc3, 0x68, 0x67, 0xf3, 0x91, 0x27, 0xda, 0xb8, 0x7d, 0x7d, 0xda, 0xf6, 0x2d, 0x5a, - 0x4d, 0x77, 0x26, 0x4b, 0xaf, 0x0d, 0x8f, 0x8c, 0xb4, 0xf9, 0xf5, 0x7a, 0xb1, 0x77, 0x01, 0xe7, - 0xde, 0xc5, 0xc7, 0x30, 0x0e, 0xf9, 0x7c, 0x9d, 0x26, 0xbe, 0xd6, 0x65, 0x98, 0xbe, 0x0d, 0x7d, - 0xbc, 0xe8, 0x7f, 0x97, 0x25, 0xc1, 0x42, 0xfe, 0x18, 0x2c, 0x8c, 0xe5, 0xa1, 0xfa, 0x43, 0x38, - 0xfb, 0x37, 0x00, 0x00, 0xff, 0xff, 0x05, 0x11, 0xba, 0x22, 0x2f, 0x08, 0x00, 0x00, + // 874 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x56, 0xef, 0x6e, 0xdb, 0x54, + 0x14, 0x9f, 0x9b, 0xb4, 0x5b, 0x4e, 0x9c, 0xa6, 0xbb, 0xcb, 0x82, 0x67, 0x6d, 0x5a, 0x30, 0x4c, + 0x8a, 0x50, 0x89, 0xa6, 0x96, 0x0f, 0x88, 0x2f, 0xa8, 0x2d, 0x1b, 0x4c, 0x62, 0x10, 0x3c, 0x01, + 0xe2, 0x93, 0x75, 0x63, 0x9f, 0x24, 0x26, 0xf6, 0xb5, 0xb1, 0xaf, 0xbb, 0xfa, 0x05, 0x78, 0x1c, + 0x9e, 0x81, 0x17, 0xe0, 0x2d, 0x78, 0x10, 0x74, 0xff, 0xb8, 0xb5, 0x13, 0x17, 0xe8, 0xb7, 0x7b, + 0x7f, 0xe7, 0x9c, 0xdf, 0xf9, 0x7b, 0x8f, 0x0d, 0x10, 0xe5, 0x69, 0x30, 0x4b, 0xb3, 0x84, 0x27, + 0xa4, 0x2b, 0xce, 0xce, 0x29, 0x3c, 0xb9, 0x58, 0x53, 0xc6, 0x30, 0x7a, 0xc3, 0x96, 0x49, 0x16, + 0x53, 0x1e, 0x26, 0xcc, 0xc5, 0xdf, 0x0a, 0xcc, 0x39, 0x19, 0xc3, 0x41, 0x5a, 0x2c, 0x36, 0x58, + 0x5a, 0xc6, 0xc4, 0x98, 0xf6, 0x5c, 0x7d, 0x73, 0xfe, 0xec, 0xc0, 0x07, 0x6d, 0x56, 0x69, 0x54, + 0x12, 0x02, 0x5d, 0x46, 0x63, 0xd4, 0x16, 0xf2, 0x5c, 0xe3, 0xd9, 0xab, 0xf3, 0x08, 0xdd, 0x75, + 0x92, 0x73, 0xab, 0xa3, 0x74, 0xc5, 0x99, 0x7c, 0x02, 0x47, 0xbe, 0xa2, 0xf6, 0x7c, 0x9a, 0x52, + 0x3f, 0xe4, 0xa5, 0xd5, 0x9d, 0x18, 0xd3, 0x8e, 0xbb, 0x83, 0x93, 0x09, 0xf4, 0x39, 0xcd, 0x56, + 0xc8, 0x3d, 0x3f, 0x61, 0x4b, 0x6b, 0x7f, 0x62, 0x4c, 0xf7, 0xdd, 0x3a, 0x44, 0x3e, 0x86, 0xc1, + 0x82, 0xe6, 0xe8, 0x2d, 0x11, 0xbd, 0x38, 0xa7, 0xdc, 0x3a, 0x90, 0x54, 0x4d, 0x90, 0xd8, 0xf0, + 0x40, 0x9c, 0x33, 0xca, 0xd1, 0xba, 0x3f, 0x31, 0xa6, 0x86, 0x7b, 0x7d, 0x27, 0x53, 0x18, 0xf2, + 0x30, 0x46, 0x2f, 0x4a, 0xfc, 0x8d, 0x17, 0x60, 0xc4, 0xa9, 0xf5, 0x60, 0x62, 0x4c, 0x07, 0xee, + 0x36, 0x2c, 0x7c, 0xc5, 0x21, 0xf3, 0xd6, 0x3c, 0xf2, 0x95, 0xaf, 0x9e, 0xf2, 0xd5, 0x00, 0xc9, + 0x09, 0x3c, 0xae, 0xf2, 0x10, 0x3e, 0x52, 0xcc, 0xe2, 0x32, 0x0b, 0x69, 0x60, 0x81, 0xd4, 0x7e, + 0xa4, 0x85, 0xaf, 0x11, 0xe7, 0x95, 0x88, 0x3c, 0x93, 0x8d, 0xf3, 0x74, 0x0d, 0xfb, 0x13, 0x63, + 0x6a, 0xba, 0xbd, 0x28, 0x4f, 0xe7, 0xaa, 0x8c, 0x27, 0xf0, 0x38, 0xa6, 0x57, 0x5e, 0xc8, 0xa8, + 0xcf, 0xc3, 0x4b, 0xf4, 0x82, 0x22, 0x93, 0x0d, 0xb1, 0x4c, 0x45, 0x19, 0xd3, 0xab, 0x37, 0x5a, + 0xf6, 0x95, 0x16, 0x39, 0xc7, 0x40, 0xbe, 0x4f, 0x91, 0xe9, 0x2e, 0xfe, 0x57, 0xc3, 0xe7, 0x70, + 0xd4, 0xd0, 0x16, 0x8d, 0xb6, 0xe0, 0x3e, 0xbf, 0xf2, 0xd6, 0x34, 0x5f, 0x6b, 0xe5, 0xea, 0x4a, + 0x1c, 0x30, 0x93, 0x82, 0xa7, 0x05, 0xf7, 0x42, 0x16, 0xe0, 0x95, 0x6c, 0xfa, 0xc0, 0x6d, 0x60, + 0xce, 0x31, 0x8c, 0x5d, 0x5c, 0x85, 0x39, 0xc7, 0x6c, 0x4e, 0xcb, 0x18, 0x19, 0xaf, 0x62, 0x20, + 0xd0, 0x5d, 0x44, 0xc9, 0x42, 0x0e, 0x85, 0xe9, 0xca, 0xb3, 0x33, 0x86, 0xd1, 0x8e, 0x76, 0x1a, + 0x95, 0xce, 0xdf, 0x06, 0x10, 0x0d, 0xd4, 0x06, 0x91, 0x7c, 0x08, 0x66, 0xaa, 0xd0, 0x9b, 0xf8, + 0x4c, 0xb7, 0xaf, 0xb1, 0x6f, 0x44, 0x8c, 0x2f, 0xe0, 0xb0, 0x52, 0xc9, 0xd1, 0xcf, 0x90, 0xcb, + 0x28, 0x4d, 0x77, 0xa0, 0xd1, 0x77, 0x12, 0x14, 0x13, 0x16, 0x60, 0xce, 0x43, 0xa6, 0x0a, 0xaa, + 0x62, 0xaa, 0x43, 0xe4, 0x25, 0x8c, 0x42, 0xe6, 0x27, 0x71, 0xc8, 0x56, 0x1e, 0x8d, 0x93, 0x82, + 0x71, 0xd5, 0x7c, 0x35, 0xb3, 0xa4, 0x92, 0x9d, 0x49, 0xd1, 0x5b, 0x31, 0x01, 0x2f, 0x61, 0x94, + 0x14, 0x7c, 0x95, 0x6c, 0x5b, 0xec, 0x2b, 0x8b, 0x4a, 0x76, 0x63, 0xe1, 0x3c, 0x87, 0xde, 0x2b, + 0xe6, 0x67, 0x65, 0xca, 0x31, 0x10, 0xf5, 0x09, 0x28, 0xa7, 0x3a, 0x29, 0x79, 0x76, 0x5c, 0x38, + 0x78, 0x17, 0xae, 0x58, 0xbb, 0x74, 0xeb, 0xf9, 0x99, 0xd7, 0xcf, 0xef, 0x29, 0xf4, 0xf2, 0x70, + 0xc5, 0x28, 0x2f, 0x32, 0xd4, 0xa9, 0xdd, 0x00, 0xce, 0xef, 0x1d, 0x18, 0x5d, 0xac, 0xd1, 0xdf, + 0xe8, 0xae, 0xe7, 0x55, 0x83, 0x5e, 0xc0, 0x21, 0xaa, 0x68, 0xbc, 0xda, 0xb0, 0x98, 0xee, 0x40, + 0xa3, 0x7a, 0x2a, 0x7f, 0x80, 0xc1, 0x92, 0x6e, 0xd0, 0xd3, 0x03, 0x9d, 0x5b, 0x7b, 0x93, 0xce, + 0xb4, 0x7f, 0x72, 0x3c, 0x93, 0x3b, 0xa8, 0x8d, 0x79, 0xf6, 0x9a, 0x6e, 0xb0, 0xc2, 0x5e, 0x31, + 0x9e, 0x95, 0xae, 0xb9, 0xac, 0x41, 0xe4, 0x57, 0x18, 0xbf, 0xa7, 0x21, 0x17, 0x85, 0xf3, 0xa3, + 0x24, 0xaf, 0x71, 0x77, 0x24, 0xf7, 0x67, 0xff, 0xc2, 0xfd, 0xb3, 0x32, 0xbc, 0x10, 0x76, 0x4d, + 0x1f, 0xa3, 0xf7, 0x2d, 0x22, 0xfb, 0x4b, 0x78, 0xb8, 0x13, 0x0e, 0x39, 0x82, 0xce, 0xcd, 0xe3, + 0x10, 0x47, 0x32, 0x82, 0xfd, 0x4b, 0x1a, 0x15, 0x28, 0x4b, 0xdb, 0x75, 0xd5, 0xe5, 0x8b, 0xbd, + 0xcf, 0x0d, 0xfb, 0x6b, 0x78, 0x72, 0xab, 0xcf, 0xbb, 0x10, 0x39, 0x7f, 0xed, 0x01, 0xd9, 0x4a, + 0x49, 0xbc, 0xbf, 0x5f, 0xe0, 0x21, 0x4b, 0xb8, 0xd7, 0xac, 0xb1, 0x21, 0xeb, 0xf0, 0x69, 0x6b, + 0x1d, 0xd2, 0xa8, 0x9c, 0x7d, 0x97, 0xf0, 0xdd, 0x22, 0x0f, 0x59, 0x13, 0x25, 0x3f, 0xc2, 0x50, + 0xd6, 0x37, 0xf8, 0x7f, 0xcd, 0x13, 0xc4, 0x32, 0xc7, 0xa0, 0xc9, 0x7b, 0xe8, 0x37, 0x40, 0xfb, + 0x1c, 0x46, 0x6d, 0xfe, 0xef, 0x54, 0xd5, 0x33, 0x78, 0xd4, 0xe2, 0xea, 0x2e, 0x14, 0x27, 0x7f, + 0xec, 0xc1, 0x40, 0x5b, 0x8b, 0xa5, 0x86, 0x19, 0xf9, 0x49, 0x14, 0x78, 0xfb, 0x73, 0x46, 0x9e, + 0x57, 0xc9, 0xde, 0xf2, 0x79, 0xb4, 0x9f, 0xdd, 0xae, 0x20, 0x96, 0xd3, 0x3d, 0x72, 0x06, 0xfd, + 0xda, 0xda, 0x24, 0x96, 0xd2, 0xdf, 0xdd, 0xbb, 0xf6, 0xb8, 0x45, 0xa2, 0x28, 0xde, 0xc2, 0x70, + 0x6b, 0xf3, 0x91, 0xa7, 0x4a, 0xb9, 0x7d, 0x7d, 0xda, 0xf6, 0x2d, 0x52, 0x45, 0x77, 0x2a, 0x52, + 0xaf, 0x35, 0x8f, 0x0c, 0x95, 0xfa, 0xf5, 0x7a, 0xb1, 0xb7, 0x01, 0xe7, 0xde, 0xf9, 0x47, 0x30, + 0x0a, 0x93, 0xd9, 0x2a, 0x4b, 0x7d, 0x25, 0xcb, 0x31, 0xbb, 0x0c, 0x7d, 0x3c, 0xef, 0x7d, 0x9b, + 0xa7, 0xc1, 0x5c, 0xfc, 0x4c, 0xcc, 0x8d, 0xc5, 0x81, 0xfc, 0xab, 0x38, 0xfd, 0x27, 0x00, 0x00, + 0xff, 0xff, 0x2a, 0xcb, 0x82, 0x79, 0x63, 0x08, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. diff --git a/rpc/lspd.proto b/rpc/lspd.proto index a1b8308..54032b6 100644 --- a/rpc/lspd.proto +++ b/rpc/lspd.proto @@ -49,6 +49,9 @@ message ChannelInformationReply { int64 channel_fee_permyriad = 10; bytes lsp_pubkey = 11; + + // The channel can be closed if not used this duration in seconds. + int64 max_inactive_duration = 12; } message OpenChannelRequest { diff --git a/server.go b/server.go index 74aaf48..8a8b323 100644 --- a/server.go +++ b/server.go @@ -38,6 +38,7 @@ const ( timeLockDelta = 144 channelFeePermyriad = 10 additionalChannelCapacity = 100_000 + maxInactiveDuration = 45 * 24 * 3600 ) type server struct{} @@ -65,6 +66,7 @@ func (s *server) ChannelInformation(ctx context.Context, in *lspdrpc.ChannelInfo TimeLockDelta: timeLockDelta, ChannelFeePermyriad: channelFeePermyriad, LspPubkey: publicKey.SerializeCompressed(), + MaxInactiveDuration: maxInactiveDuration, }, nil } From ab2be3664213ce3e6fa3f7a5d993c01f33110491 Mon Sep 17 00:00:00 2001 From: Yaacov Akiba Slama Date: Thu, 11 Mar 2021 17:04:15 +0200 Subject: [PATCH 048/214] Change OpenChannel endpoint to open public channels --- server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server.go b/server.go index 8a8b323..ed15540 100644 --- a/server.go +++ b/server.go @@ -117,7 +117,7 @@ func (s *server) OpenChannel(ctx context.Context, in *lspdrpc.OpenChannelRequest PushSat: 0, TargetConf: targetConf, MinHtlcMsat: minHtlcMsat, - Private: true, + Private: false, }) log.Printf("Response from OpenChannel: %#v (TX: %v)", response, hex.EncodeToString(response.GetFundingTxidBytes())) From 36001958e267efadd05c407f0042c9f007eed9f6 Mon Sep 17 00:00:00 2001 From: Yaacov Akiba Slama Date: Sun, 14 Mar 2021 16:05:03 +0200 Subject: [PATCH 049/214] Use a target of 6 blocks when opening a public channel --- server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server.go b/server.go index ed15540..a8fa041 100644 --- a/server.go +++ b/server.go @@ -31,7 +31,7 @@ import ( const ( channelAmount = 1_000_000 - targetConf = 1 + targetConf = 6 minHtlcMsat = 600 baseFeeMsat = 1000 feeRate = 0.000001 From 78b9f2fdc432c21ee5201b0736e17e1ff1853c36 Mon Sep 17 00:00:00 2001 From: Yaacov Akiba Slama Date: Mon, 15 Mar 2021 20:06:55 +0200 Subject: [PATCH 050/214] Use an unique amount --- server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server.go b/server.go index a8fa041..17b0764 100644 --- a/server.go +++ b/server.go @@ -30,7 +30,7 @@ import ( ) const ( - channelAmount = 1_000_000 + channelAmount = 1_000_183 targetConf = 6 minHtlcMsat = 600 baseFeeMsat = 1000 From 76f51e2ba7d9979abdc81e79f614300ff93852a3 Mon Sep 17 00:00:00 2001 From: Yaacov Akiba Slama Date: Tue, 16 Mar 2021 10:55:35 +0200 Subject: [PATCH 051/214] Ensure that private and public channels have different capacity --- intercept.go | 3 +++ server.go | 6 +++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/intercept.go b/intercept.go index 68bdb80..60d8afb 100644 --- a/intercept.go +++ b/intercept.go @@ -52,6 +52,9 @@ func isConnected(ctx context.Context, client lnrpc.LightningClient, destination func openChannel(ctx context.Context, client lnrpc.LightningClient, paymentHash, destination []byte, incomingAmountMsat int64) ([]byte, uint32, error) { capacity := incomingAmountMsat/1000 + additionalChannelCapacity + if capacity == publicChannelAmount { + capacity++ + } channelPoint, err := client.OpenChannelSync(ctx, &lnrpc.OpenChannelRequest{ NodePubkey: destination, LocalFundingAmount: capacity, diff --git a/server.go b/server.go index 17b0764..89b01ee 100644 --- a/server.go +++ b/server.go @@ -30,7 +30,7 @@ import ( ) const ( - channelAmount = 1_000_183 + publicChannelAmount = 1_000_183 targetConf = 6 minHtlcMsat = 600 baseFeeMsat = 1000 @@ -58,7 +58,7 @@ func (s *server) ChannelInformation(ctx context.Context, in *lspdrpc.ChannelInfo Name: nodeName, Pubkey: nodePubkey, Host: os.Getenv("NODE_HOST"), - ChannelCapacity: channelAmount, + ChannelCapacity: publicChannelAmount, TargetConf: targetConf, MinHtlcMsat: minHtlcMsat, BaseFeeMsat: baseFeeMsat, @@ -112,7 +112,7 @@ func (s *server) OpenChannel(ctx context.Context, in *lspdrpc.OpenChannelRequest var outputIndex uint32 if len(nodeChannels) == 0 && len(pendingChannels) == 0 { response, err := client.OpenChannelSync(clientCtx, &lnrpc.OpenChannelRequest{ - LocalFundingAmount: channelAmount, + LocalFundingAmount: publicChannelAmount, NodePubkeyString: in.Pubkey, PushSat: 0, TargetConf: targetConf, From 8ca6ca87fdbfee3d60628df4ca0ed08877ad8895 Mon Sep 17 00:00:00 2001 From: Yaacov Akiba Slama Date: Fri, 19 Mar 2021 13:47:36 +0200 Subject: [PATCH 052/214] Synchronize private channels from lnd to the channels table every hour --- db.go | 3 ++- forwarding_history.go | 35 +++++++++++++++++++++++++++++++++++ server.go | 1 + 3 files changed, 38 insertions(+), 1 deletion(-) diff --git a/db.go b/db.go index f28f883..a2aeec5 100644 --- a/db.go +++ b/db.go @@ -74,7 +74,8 @@ func insertChannel(chanID uint64, channelPoint string, nodeID []byte) error { _, err := pgxPool.Exec(context.Background(), `INSERT INTO channels (chanid, channel_point, nodeid) - VALUES ($1, $2, $3)`, + VALUES ($1, $2, $3) + ON CONFLICT (chanid) DO NOTHING`, chanID, channelPoint, nodeID) if err != nil { return fmt.Errorf("insertChannel(%v, %s, %x) error: %w", diff --git a/forwarding_history.go b/forwarding_history.go index 5ee298f..48bc35d 100644 --- a/forwarding_history.go +++ b/forwarding_history.go @@ -2,6 +2,7 @@ package main import ( "context" + "encoding/hex" "fmt" "log" "os" @@ -35,6 +36,40 @@ func (cfe *copyFromEvents) Err() error { return cfe.err } +func channelsSynchronize() { + for { + err := channelsSynchronizeOnce() + log.Printf("channelsSynchronizeOnce() err: %v", err) + time.Sleep(1 * time.Hour) + } +} + +func channelsSynchronizeOnce() error { + log.Printf("channelsSynchronizeOnce - begin") + clientCtx := metadata.AppendToOutgoingContext(context.Background(), "macaroon", os.Getenv("LND_MACAROON_HEX")) + channels, err := client.ListChannels(clientCtx, &lnrpc.ListChannelsRequest{PrivateOnly: true}) + if err != nil { + log.Printf("ListChannels error: %v", err) + return fmt.Errorf("client.ListChannels() error: %w", err) + } + log.Printf("channelsSynchronizeOnce - received channels") + for _, c := range channels.Channels { + nodeID, err := hex.DecodeString(c.RemotePubkey) + if err != nil { + log.Printf("hex.DecodeString in channelsSynchronizeOnce error: %v", err) + continue + } + err = insertChannel(c.ChanId, c.ChannelPoint, nodeID) + if err != nil { + log.Printf("insertChannel(%v, %v, %x) in channelsSynchronizeOnce error: %v", c.ChanId, c.ChannelPoint, nodeID, err) + continue + } + } + log.Printf("channelsSynchronizeOnce - done") + + return nil +} + func forwardingHistorySynchronize() { for { err := forwardingHistorySynchronizeOnce() diff --git a/server.go b/server.go index 89b01ee..ec6cba0 100644 --- a/server.go +++ b/server.go @@ -379,6 +379,7 @@ func main() { go intercept() go forwardingHistorySynchronize() + go channelsSynchronize() s := grpc.NewServer( grpc_middleware.WithUnaryServerChain(func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { From ee75d838a143c4970307abbac0093d91d00ee533 Mon Sep 17 00:00:00 2001 From: Yaacov Akiba Slama Date: Mon, 5 Apr 2021 18:31:02 +0300 Subject: [PATCH 053/214] Add field last_update to channels --- db.go | 11 ++++++----- forwarding_history.go | 3 ++- intercept.go | 2 +- .../migrations/000007_channels_last_update.down.sql | 1 + .../migrations/000007_channels_last_update.up.sql | 1 + 5 files changed, 11 insertions(+), 7 deletions(-) create mode 100644 postgresql/migrations/000007_channels_last_update.down.sql create mode 100644 postgresql/migrations/000007_channels_last_update.up.sql diff --git a/db.go b/db.go index a2aeec5..42dd66c 100644 --- a/db.go +++ b/db.go @@ -5,6 +5,7 @@ import ( "fmt" "log" "os" + "time" "github.com/jackc/pgtype" "github.com/jackc/pgx/v4" @@ -70,13 +71,13 @@ func registerPayment(destination, paymentHash, paymentSecret []byte, incomingAmo return nil } -func insertChannel(chanID uint64, channelPoint string, nodeID []byte) error { +func insertChannel(chanID uint64, channelPoint string, nodeID []byte, lastUpdate time.Time) error { _, err := pgxPool.Exec(context.Background(), `INSERT INTO - channels (chanid, channel_point, nodeid) - VALUES ($1, $2, $3) - ON CONFLICT (chanid) DO NOTHING`, - chanID, channelPoint, nodeID) + channels (chanid, channel_point, nodeid, last_update) + VALUES ($1, $2, $3, $4) + ON CONFLICT (chanid) DO UPDATE SET last_update=$4`, + chanID, channelPoint, nodeID, lastUpdate) if err != nil { return fmt.Errorf("insertChannel(%v, %s, %x) error: %w", chanID, channelPoint, nodeID, err) diff --git a/forwarding_history.go b/forwarding_history.go index 48bc35d..4310d8f 100644 --- a/forwarding_history.go +++ b/forwarding_history.go @@ -53,13 +53,14 @@ func channelsSynchronizeOnce() error { return fmt.Errorf("client.ListChannels() error: %w", err) } log.Printf("channelsSynchronizeOnce - received channels") + lastUpdate := time.Now() for _, c := range channels.Channels { nodeID, err := hex.DecodeString(c.RemotePubkey) if err != nil { log.Printf("hex.DecodeString in channelsSynchronizeOnce error: %v", err) continue } - err = insertChannel(c.ChanId, c.ChannelPoint, nodeID) + err = insertChannel(c.ChanId, c.ChannelPoint, nodeID, lastUpdate) if err != nil { log.Printf("insertChannel(%v, %v, %x) in channelsSynchronizeOnce error: %v", c.ChanId, c.ChannelPoint, nodeID, err) continue diff --git a/intercept.go b/intercept.go index 60d8afb..6cfa877 100644 --- a/intercept.go +++ b/intercept.go @@ -243,7 +243,7 @@ func resumeOrCancel( OutgoingRequestedChanId: chanID, OnionBlob: onionBlob, }) - err := insertChannel(chanID, channelPoint, destination) + err := insertChannel(chanID, channelPoint, destination, time.Now()) if err != nil { log.Printf("insertChannel error: %v", err) } diff --git a/postgresql/migrations/000007_channels_last_update.down.sql b/postgresql/migrations/000007_channels_last_update.down.sql new file mode 100644 index 0000000..4442e51 --- /dev/null +++ b/postgresql/migrations/000007_channels_last_update.down.sql @@ -0,0 +1 @@ +ALTER TABLE public.channels DROP COLUMN last_update; \ No newline at end of file diff --git a/postgresql/migrations/000007_channels_last_update.up.sql b/postgresql/migrations/000007_channels_last_update.up.sql new file mode 100644 index 0000000..83db788 --- /dev/null +++ b/postgresql/migrations/000007_channels_last_update.up.sql @@ -0,0 +1 @@ +ALTER TABLE public.channels ADD COLUMN last_update TIMESTAMP; \ No newline at end of file From 133c87ec27d08d7ded0d334e26ae812c7771fcef Mon Sep 17 00:00:00 2001 From: Yaacov Akiba Slama Date: Sun, 9 May 2021 21:07:23 +0300 Subject: [PATCH 054/214] Don't fail when adding twice the same payment_hash --- db.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/db.go b/db.go index 42dd66c..f26f92c 100644 --- a/db.go +++ b/db.go @@ -60,7 +60,8 @@ func registerPayment(destination, paymentHash, paymentSecret []byte, incomingAmo commandTag, err := pgxPool.Exec(context.Background(), `INSERT INTO payments (destination, payment_hash, payment_secret, incoming_amount_msat, outgoing_amount_msat) - VALUES ($1, $2, $3, $4, $5)`, + VALUES ($1, $2, $3, $4, $5) + ON CONFLICT DO NOTHING`, destination, paymentHash, paymentSecret, incomingAmountMsat, outgoingAmountMsat) log.Printf("registerPayment(%x, %x, %x, %v, %v) rows: %v err: %v", destination, paymentHash, paymentSecret, incomingAmountMsat, outgoingAmountMsat, commandTag.RowsAffected(), err) From ad31aa892113e2538f55d782bef9085ea71fc479 Mon Sep 17 00:00:00 2001 From: Yaacov Akiba Slama Date: Mon, 24 May 2021 12:24:54 +0300 Subject: [PATCH 055/214] Synchronize channels to DB after each block and check channels from DB --- db.go | 34 ++++++++++++++++++++++++++++++++++ forwarding_history.go | 29 +++++++++++++++++++++++++---- go.mod | 1 + server.go | 22 ++++++++++++++-------- 4 files changed, 74 insertions(+), 12 deletions(-) diff --git a/db.go b/db.go index f26f92c..acc3202 100644 --- a/db.go +++ b/db.go @@ -2,6 +2,7 @@ package main import ( "context" + "encoding/hex" "fmt" "log" "os" @@ -10,6 +11,7 @@ import ( "github.com/jackc/pgtype" "github.com/jackc/pgx/v4" "github.com/jackc/pgx/v4/pgxpool" + "github.com/lightningnetwork/lnd/lnwire" ) var ( @@ -86,6 +88,38 @@ func insertChannel(chanID uint64, channelPoint string, nodeID []byte, lastUpdate return nil } +func confirmedChannels(sNodeID string) (map[string]uint64, error) { + nodeID, err := hex.DecodeString(sNodeID) + if err != nil { + return nil, fmt.Errorf("hex.DecodeString(%v) error: %w", sNodeID, err) + } + rows, err := pgxPool.Query(context.Background(), + `SELECT chanid, channel_point + FROM channels + WHERE nodeid=$1`, + nodeID) + if err != nil { + return nil, fmt.Errorf("channels(%x) error: %w", nodeID, err) + } + defer rows.Close() + chans := make(map[string]uint64) + for rows.Next() { + var ( + chanID uint64 + channelPoint string + ) + err = rows.Scan(&chanID, &channelPoint) + if err != nil { + return nil, fmt.Errorf("channels(%x) rows.Scan error: %w", nodeID, err) + } + sid := lnwire.NewShortChanIDFromInt(chanID) + if !sid.IsFake() { + chans[channelPoint] = chanID + } + } + return chans, rows.Err() +} + func lastForwardingEvent() (int64, error) { var last int64 err := pgxPool.QueryRow(context.Background(), diff --git a/forwarding_history.go b/forwarding_history.go index 4310d8f..ac35725 100644 --- a/forwarding_history.go +++ b/forwarding_history.go @@ -9,6 +9,7 @@ import ( "time" "github.com/lightningnetwork/lnd/lnrpc" + "github.com/lightningnetwork/lnd/lnrpc/chainrpc" "google.golang.org/grpc/metadata" ) @@ -36,11 +37,31 @@ func (cfe *copyFromEvents) Err() error { return cfe.err } -func channelsSynchronize() { +func channelsSynchronize(client chainrpc.ChainNotifierClient) { + lastSync := time.Now().Add(-6 * time.Minute) for { - err := channelsSynchronizeOnce() - log.Printf("channelsSynchronizeOnce() err: %v", err) - time.Sleep(1 * time.Hour) + cancellableCtx, cancel := context.WithCancel(context.Background()) + clientCtx := metadata.AppendToOutgoingContext(cancellableCtx, "macaroon", os.Getenv("LND_MACAROON_HEX")) + stream, err := client.RegisterBlockEpochNtfn(clientCtx, &chainrpc.BlockEpoch{}) + if err != nil { + log.Printf("chainNotifierClient.RegisterBlockEpochNtfn(): %v", err) + cancel() + } + + for { + _, err := stream.Recv() + if err != nil { + log.Printf("stream.Recv: %v", err) + break + } + if lastSync.Add(5 * time.Minute).Before(time.Now()) { + time.Sleep(30 * time.Second) + err = channelsSynchronizeOnce() + lastSync = time.Now() + log.Printf("channelsSynchronizeOnce() err: %v", err) + } + } + cancel() } } diff --git a/go.mod b/go.mod index 7573a3a..f3f10eb 100644 --- a/go.mod +++ b/go.mod @@ -17,6 +17,7 @@ require ( github.com/lightningnetwork/lnd v0.11.0-beta golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e google.golang.org/grpc v1.29.1 + google.golang.org/protobuf v1.23.0 ) replace github.com/lightningnetwork/lnd v0.11.0-beta => github.com/breez/lnd v0.11.0-beta.rc4.0.20210125150416-0c10146b223c diff --git a/server.go b/server.go index ec6cba0..d3732b2 100644 --- a/server.go +++ b/server.go @@ -19,6 +19,7 @@ import ( "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" @@ -46,6 +47,7 @@ type server struct{} var ( client lnrpc.LightningClient routerClient routerrpc.RouterClient + chainNotifierClient chainrpc.ChainNotifierClient openChannelReqGroup singleflight.Group privateKey *btcec.PrivateKey publicKey *btcec.PublicKey @@ -229,16 +231,16 @@ func (s *server) CheckChannels(ctx context.Context, in *lspdrpc.Encrypted) (*lsp func getNotFakeChannels(nodeID string, channelPoints map[string]uint64) (map[string]uint64, error) { r := make(map[string]uint64) - channels, err := getNodeChannels(nodeID) + if len(channelPoints) == 0 { + return r, nil + } + channels, err := confirmedChannels(nodeID) if err != nil { return nil, err } - for _, c := range channels { - if _, ok := channelPoints[c.ChannelPoint]; ok { - sid := lnwire.NewShortChanIDFromInt(c.ChanId) - if !sid.IsFake() { - r[c.ChannelPoint] = c.ChanId - } + for channelPoint, chanID := range channels { + if _, ok := channelPoints[channelPoint]; ok { + r[channelPoint] = chanID } } return r, nil @@ -246,6 +248,9 @@ func getNotFakeChannels(nodeID string, channelPoints map[string]uint64) (map[str 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 @@ -363,6 +368,7 @@ func main() { 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{}) @@ -379,7 +385,7 @@ func main() { go intercept() go forwardingHistorySynchronize() - go channelsSynchronize() + go channelsSynchronize(chainNotifierClient) s := grpc.NewServer( grpc_middleware.WithUnaryServerChain(func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { From 193e3639d972ccac53d7b9802d0f5b644300bca8 Mon Sep 17 00:00:00 2001 From: Yaacov Akiba Slama Date: Sun, 30 May 2021 12:19:43 +0300 Subject: [PATCH 056/214] Disable email sending when channel mismatch --- server.go | 6 ------ 1 file changed, 6 deletions(-) diff --git a/server.go b/server.go index d3732b2..6948874 100644 --- a/server.go +++ b/server.go @@ -201,12 +201,6 @@ func (s *server) CheckChannels(ctx context.Context, in *lspdrpc.Encrypted) (*lsp log.Printf("getNotFakeChannels(%v) error: %v", checkChannelsRequest.FakeChannels, err) return nil, fmt.Errorf("getNotFakeChannels(%v) error: %w", checkChannelsRequest.FakeChannels, err) } - if len(notFakeChannels) != 0 || len(closedChannels) != 0 { - err = sendChannelMismatchNotification(nodeID, notFakeChannels, closedChannels) - if err != nil { - log.Printf("sendChannelMismatchNotification() error: %v", err) - } - } checkChannelsReply := lspdrpc.CheckChannelsReply{ NotFakeChannels: notFakeChannels, ClosedChannels: closedChannels, From b86a77ab69258fa15f6329fa0e9846e046ef8685 Mon Sep 17 00:00:00 2001 From: Yaacov Akiba Slama Date: Wed, 16 Jun 2021 18:33:01 +0300 Subject: [PATCH 057/214] Add channel creation minimum fees --- intercept.go | 3 ++ rpc/lspd.pb.go | 130 ++++++++++++++++++++++++++----------------------- rpc/lspd.proto | 2 + server.go | 26 +++++----- 4 files changed, 89 insertions(+), 72 deletions(-) diff --git a/intercept.go b/intercept.go index 6cfa877..3e529f2 100644 --- a/intercept.go +++ b/intercept.go @@ -27,6 +27,9 @@ import ( func checkPayment(incomingAmountMsat, outgoingAmountMsat int64) error { fees := incomingAmountMsat * channelFeePermyriad / 10_000 / 1_000 * 1_000 + if fees < channelMinimumFeeMsat { + fees = channelMinimumFeeMsat + } if incomingAmountMsat-outgoingAmountMsat < fees { return fmt.Errorf("not enough fees") } diff --git a/rpc/lspd.pb.go b/rpc/lspd.pb.go index e3262f3..be1c80e 100644 --- a/rpc/lspd.pb.go +++ b/rpc/lspd.pb.go @@ -90,10 +90,11 @@ type ChannelInformationReply struct { ChannelFeePermyriad int64 `protobuf:"varint,10,opt,name=channel_fee_permyriad,json=channelFeePermyriad,proto3" json:"channel_fee_permyriad,omitempty"` LspPubkey []byte `protobuf:"bytes,11,opt,name=lsp_pubkey,json=lspPubkey,proto3" json:"lsp_pubkey,omitempty"` // The channel can be closed if not used this duration in seconds. - MaxInactiveDuration int64 `protobuf:"varint,12,opt,name=max_inactive_duration,json=maxInactiveDuration,proto3" json:"max_inactive_duration,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + MaxInactiveDuration int64 `protobuf:"varint,12,opt,name=max_inactive_duration,json=maxInactiveDuration,proto3" json:"max_inactive_duration,omitempty"` + ChannelMinimumFeeMsat int64 `protobuf:"varint,13,opt,name=channel_minimum_fee_msat,json=channelMinimumFeeMsat,proto3" json:"channel_minimum_fee_msat,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } func (m *ChannelInformationReply) Reset() { *m = ChannelInformationReply{} } @@ -205,6 +206,13 @@ func (m *ChannelInformationReply) GetMaxInactiveDuration() int64 { return 0 } +func (m *ChannelInformationReply) GetChannelMinimumFeeMsat() int64 { + if m != nil { + return m.ChannelMinimumFeeMsat + } + return 0 +} + type OpenChannelRequest struct { /// The identity pubkey of the Lightning node Pubkey string `protobuf:"bytes,1,opt,name=pubkey,proto3" json:"pubkey,omitempty"` @@ -654,62 +662,64 @@ func init() { } var fileDescriptor_c69a0f5a734bca26 = []byte{ - // 874 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x56, 0xef, 0x6e, 0xdb, 0x54, - 0x14, 0x9f, 0x9b, 0xb4, 0x5b, 0x4e, 0x9c, 0xa6, 0xbb, 0xcb, 0x82, 0x67, 0x6d, 0x5a, 0x30, 0x4c, - 0x8a, 0x50, 0x89, 0xa6, 0x96, 0x0f, 0x88, 0x2f, 0xa8, 0x2d, 0x1b, 0x4c, 0x62, 0x10, 0x3c, 0x01, - 0xe2, 0x93, 0x75, 0x63, 0x9f, 0x24, 0x26, 0xf6, 0xb5, 0xb1, 0xaf, 0xbb, 0xfa, 0x05, 0x78, 0x1c, - 0x9e, 0x81, 0x17, 0xe0, 0x2d, 0x78, 0x10, 0x74, 0xff, 0xb8, 0xb5, 0x13, 0x17, 0xe8, 0xb7, 0x7b, - 0x7f, 0xe7, 0x9c, 0xdf, 0xf9, 0x7b, 0x8f, 0x0d, 0x10, 0xe5, 0x69, 0x30, 0x4b, 0xb3, 0x84, 0x27, - 0xa4, 0x2b, 0xce, 0xce, 0x29, 0x3c, 0xb9, 0x58, 0x53, 0xc6, 0x30, 0x7a, 0xc3, 0x96, 0x49, 0x16, - 0x53, 0x1e, 0x26, 0xcc, 0xc5, 0xdf, 0x0a, 0xcc, 0x39, 0x19, 0xc3, 0x41, 0x5a, 0x2c, 0x36, 0x58, - 0x5a, 0xc6, 0xc4, 0x98, 0xf6, 0x5c, 0x7d, 0x73, 0xfe, 0xec, 0xc0, 0x07, 0x6d, 0x56, 0x69, 0x54, - 0x12, 0x02, 0x5d, 0x46, 0x63, 0xd4, 0x16, 0xf2, 0x5c, 0xe3, 0xd9, 0xab, 0xf3, 0x08, 0xdd, 0x75, - 0x92, 0x73, 0xab, 0xa3, 0x74, 0xc5, 0x99, 0x7c, 0x02, 0x47, 0xbe, 0xa2, 0xf6, 0x7c, 0x9a, 0x52, - 0x3f, 0xe4, 0xa5, 0xd5, 0x9d, 0x18, 0xd3, 0x8e, 0xbb, 0x83, 0x93, 0x09, 0xf4, 0x39, 0xcd, 0x56, - 0xc8, 0x3d, 0x3f, 0x61, 0x4b, 0x6b, 0x7f, 0x62, 0x4c, 0xf7, 0xdd, 0x3a, 0x44, 0x3e, 0x86, 0xc1, - 0x82, 0xe6, 0xe8, 0x2d, 0x11, 0xbd, 0x38, 0xa7, 0xdc, 0x3a, 0x90, 0x54, 0x4d, 0x90, 0xd8, 0xf0, - 0x40, 0x9c, 0x33, 0xca, 0xd1, 0xba, 0x3f, 0x31, 0xa6, 0x86, 0x7b, 0x7d, 0x27, 0x53, 0x18, 0xf2, - 0x30, 0x46, 0x2f, 0x4a, 0xfc, 0x8d, 0x17, 0x60, 0xc4, 0xa9, 0xf5, 0x60, 0x62, 0x4c, 0x07, 0xee, - 0x36, 0x2c, 0x7c, 0xc5, 0x21, 0xf3, 0xd6, 0x3c, 0xf2, 0x95, 0xaf, 0x9e, 0xf2, 0xd5, 0x00, 0xc9, - 0x09, 0x3c, 0xae, 0xf2, 0x10, 0x3e, 0x52, 0xcc, 0xe2, 0x32, 0x0b, 0x69, 0x60, 0x81, 0xd4, 0x7e, - 0xa4, 0x85, 0xaf, 0x11, 0xe7, 0x95, 0x88, 0x3c, 0x93, 0x8d, 0xf3, 0x74, 0x0d, 0xfb, 0x13, 0x63, - 0x6a, 0xba, 0xbd, 0x28, 0x4f, 0xe7, 0xaa, 0x8c, 0x27, 0xf0, 0x38, 0xa6, 0x57, 0x5e, 0xc8, 0xa8, - 0xcf, 0xc3, 0x4b, 0xf4, 0x82, 0x22, 0x93, 0x0d, 0xb1, 0x4c, 0x45, 0x19, 0xd3, 0xab, 0x37, 0x5a, - 0xf6, 0x95, 0x16, 0x39, 0xc7, 0x40, 0xbe, 0x4f, 0x91, 0xe9, 0x2e, 0xfe, 0x57, 0xc3, 0xe7, 0x70, - 0xd4, 0xd0, 0x16, 0x8d, 0xb6, 0xe0, 0x3e, 0xbf, 0xf2, 0xd6, 0x34, 0x5f, 0x6b, 0xe5, 0xea, 0x4a, - 0x1c, 0x30, 0x93, 0x82, 0xa7, 0x05, 0xf7, 0x42, 0x16, 0xe0, 0x95, 0x6c, 0xfa, 0xc0, 0x6d, 0x60, - 0xce, 0x31, 0x8c, 0x5d, 0x5c, 0x85, 0x39, 0xc7, 0x6c, 0x4e, 0xcb, 0x18, 0x19, 0xaf, 0x62, 0x20, - 0xd0, 0x5d, 0x44, 0xc9, 0x42, 0x0e, 0x85, 0xe9, 0xca, 0xb3, 0x33, 0x86, 0xd1, 0x8e, 0x76, 0x1a, - 0x95, 0xce, 0xdf, 0x06, 0x10, 0x0d, 0xd4, 0x06, 0x91, 0x7c, 0x08, 0x66, 0xaa, 0xd0, 0x9b, 0xf8, - 0x4c, 0xb7, 0xaf, 0xb1, 0x6f, 0x44, 0x8c, 0x2f, 0xe0, 0xb0, 0x52, 0xc9, 0xd1, 0xcf, 0x90, 0xcb, - 0x28, 0x4d, 0x77, 0xa0, 0xd1, 0x77, 0x12, 0x14, 0x13, 0x16, 0x60, 0xce, 0x43, 0xa6, 0x0a, 0xaa, - 0x62, 0xaa, 0x43, 0xe4, 0x25, 0x8c, 0x42, 0xe6, 0x27, 0x71, 0xc8, 0x56, 0x1e, 0x8d, 0x93, 0x82, - 0x71, 0xd5, 0x7c, 0x35, 0xb3, 0xa4, 0x92, 0x9d, 0x49, 0xd1, 0x5b, 0x31, 0x01, 0x2f, 0x61, 0x94, - 0x14, 0x7c, 0x95, 0x6c, 0x5b, 0xec, 0x2b, 0x8b, 0x4a, 0x76, 0x63, 0xe1, 0x3c, 0x87, 0xde, 0x2b, - 0xe6, 0x67, 0x65, 0xca, 0x31, 0x10, 0xf5, 0x09, 0x28, 0xa7, 0x3a, 0x29, 0x79, 0x76, 0x5c, 0x38, - 0x78, 0x17, 0xae, 0x58, 0xbb, 0x74, 0xeb, 0xf9, 0x99, 0xd7, 0xcf, 0xef, 0x29, 0xf4, 0xf2, 0x70, - 0xc5, 0x28, 0x2f, 0x32, 0xd4, 0xa9, 0xdd, 0x00, 0xce, 0xef, 0x1d, 0x18, 0x5d, 0xac, 0xd1, 0xdf, - 0xe8, 0xae, 0xe7, 0x55, 0x83, 0x5e, 0xc0, 0x21, 0xaa, 0x68, 0xbc, 0xda, 0xb0, 0x98, 0xee, 0x40, - 0xa3, 0x7a, 0x2a, 0x7f, 0x80, 0xc1, 0x92, 0x6e, 0xd0, 0xd3, 0x03, 0x9d, 0x5b, 0x7b, 0x93, 0xce, - 0xb4, 0x7f, 0x72, 0x3c, 0x93, 0x3b, 0xa8, 0x8d, 0x79, 0xf6, 0x9a, 0x6e, 0xb0, 0xc2, 0x5e, 0x31, - 0x9e, 0x95, 0xae, 0xb9, 0xac, 0x41, 0xe4, 0x57, 0x18, 0xbf, 0xa7, 0x21, 0x17, 0x85, 0xf3, 0xa3, - 0x24, 0xaf, 0x71, 0x77, 0x24, 0xf7, 0x67, 0xff, 0xc2, 0xfd, 0xb3, 0x32, 0xbc, 0x10, 0x76, 0x4d, - 0x1f, 0xa3, 0xf7, 0x2d, 0x22, 0xfb, 0x4b, 0x78, 0xb8, 0x13, 0x0e, 0x39, 0x82, 0xce, 0xcd, 0xe3, - 0x10, 0x47, 0x32, 0x82, 0xfd, 0x4b, 0x1a, 0x15, 0x28, 0x4b, 0xdb, 0x75, 0xd5, 0xe5, 0x8b, 0xbd, - 0xcf, 0x0d, 0xfb, 0x6b, 0x78, 0x72, 0xab, 0xcf, 0xbb, 0x10, 0x39, 0x7f, 0xed, 0x01, 0xd9, 0x4a, - 0x49, 0xbc, 0xbf, 0x5f, 0xe0, 0x21, 0x4b, 0xb8, 0xd7, 0xac, 0xb1, 0x21, 0xeb, 0xf0, 0x69, 0x6b, - 0x1d, 0xd2, 0xa8, 0x9c, 0x7d, 0x97, 0xf0, 0xdd, 0x22, 0x0f, 0x59, 0x13, 0x25, 0x3f, 0xc2, 0x50, - 0xd6, 0x37, 0xf8, 0x7f, 0xcd, 0x13, 0xc4, 0x32, 0xc7, 0xa0, 0xc9, 0x7b, 0xe8, 0x37, 0x40, 0xfb, - 0x1c, 0x46, 0x6d, 0xfe, 0xef, 0x54, 0xd5, 0x33, 0x78, 0xd4, 0xe2, 0xea, 0x2e, 0x14, 0x27, 0x7f, - 0xec, 0xc1, 0x40, 0x5b, 0x8b, 0xa5, 0x86, 0x19, 0xf9, 0x49, 0x14, 0x78, 0xfb, 0x73, 0x46, 0x9e, - 0x57, 0xc9, 0xde, 0xf2, 0x79, 0xb4, 0x9f, 0xdd, 0xae, 0x20, 0x96, 0xd3, 0x3d, 0x72, 0x06, 0xfd, - 0xda, 0xda, 0x24, 0x96, 0xd2, 0xdf, 0xdd, 0xbb, 0xf6, 0xb8, 0x45, 0xa2, 0x28, 0xde, 0xc2, 0x70, - 0x6b, 0xf3, 0x91, 0xa7, 0x4a, 0xb9, 0x7d, 0x7d, 0xda, 0xf6, 0x2d, 0x52, 0x45, 0x77, 0x2a, 0x52, - 0xaf, 0x35, 0x8f, 0x0c, 0x95, 0xfa, 0xf5, 0x7a, 0xb1, 0xb7, 0x01, 0xe7, 0xde, 0xf9, 0x47, 0x30, - 0x0a, 0x93, 0xd9, 0x2a, 0x4b, 0x7d, 0x25, 0xcb, 0x31, 0xbb, 0x0c, 0x7d, 0x3c, 0xef, 0x7d, 0x9b, - 0xa7, 0xc1, 0x5c, 0xfc, 0x4c, 0xcc, 0x8d, 0xc5, 0x81, 0xfc, 0xab, 0x38, 0xfd, 0x27, 0x00, 0x00, - 0xff, 0xff, 0x2a, 0xcb, 0x82, 0x79, 0x63, 0x08, 0x00, 0x00, + // 899 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x56, 0xef, 0x6e, 0xe3, 0x44, + 0x10, 0x3f, 0x37, 0x69, 0xef, 0x32, 0x71, 0x9a, 0xde, 0x5e, 0x2e, 0xf8, 0xa2, 0x3b, 0x5d, 0x30, + 0x9c, 0x14, 0xa1, 0x12, 0xa1, 0x16, 0x09, 0xc4, 0x17, 0xd4, 0x96, 0x2b, 0x9c, 0x44, 0x21, 0xf8, + 0x04, 0x88, 0x4f, 0xd6, 0xc6, 0x9e, 0x24, 0x4b, 0xec, 0xb5, 0xb1, 0xd7, 0xbd, 0xe6, 0x05, 0x78, + 0x1c, 0xde, 0x84, 0xb7, 0xe0, 0x25, 0xf8, 0x86, 0xf6, 0x8f, 0x1b, 0x27, 0x71, 0x81, 0x7e, 0x5b, + 0xff, 0x66, 0xe6, 0x37, 0xb3, 0xbf, 0x99, 0x9d, 0x04, 0x20, 0xca, 0xd3, 0x70, 0x9c, 0x66, 0x89, + 0x48, 0x48, 0x53, 0x9e, 0xdd, 0x53, 0x78, 0x76, 0xb1, 0xa0, 0x9c, 0x63, 0xf4, 0x86, 0xcf, 0x92, + 0x2c, 0xa6, 0x82, 0x25, 0xdc, 0xc3, 0xdf, 0x0a, 0xcc, 0x05, 0xe9, 0xc3, 0x41, 0x5a, 0x4c, 0x97, + 0xb8, 0x72, 0xac, 0xa1, 0x35, 0x6a, 0x79, 0xe6, 0xcb, 0xfd, 0xbb, 0x01, 0xef, 0xd5, 0x45, 0xa5, + 0xd1, 0x8a, 0x10, 0x68, 0x72, 0x1a, 0xa3, 0x89, 0x50, 0xe7, 0x0a, 0xcf, 0x5e, 0x95, 0x47, 0xfa, + 0x2e, 0x92, 0x5c, 0x38, 0x0d, 0xed, 0x2b, 0xcf, 0xe4, 0x23, 0x38, 0x0a, 0x34, 0xb5, 0x1f, 0xd0, + 0x94, 0x06, 0x4c, 0xac, 0x9c, 0xe6, 0xd0, 0x1a, 0x35, 0xbc, 0x1d, 0x9c, 0x0c, 0xa1, 0x2d, 0x68, + 0x36, 0x47, 0xe1, 0x07, 0x09, 0x9f, 0x39, 0xfb, 0x43, 0x6b, 0xb4, 0xef, 0x55, 0x21, 0xf2, 0x21, + 0x74, 0xa6, 0x34, 0x47, 0x7f, 0x86, 0xe8, 0xc7, 0x39, 0x15, 0xce, 0x81, 0xa2, 0xda, 0x04, 0xc9, + 0x00, 0x1e, 0xc9, 0x73, 0x46, 0x05, 0x3a, 0x0f, 0x87, 0xd6, 0xc8, 0xf2, 0x6e, 0xbf, 0xc9, 0x08, + 0xba, 0x82, 0xc5, 0xe8, 0x47, 0x49, 0xb0, 0xf4, 0x43, 0x8c, 0x04, 0x75, 0x1e, 0x0d, 0xad, 0x51, + 0xc7, 0xdb, 0x86, 0x65, 0xae, 0x98, 0x71, 0x7f, 0x21, 0xa2, 0x40, 0xe7, 0x6a, 0xe9, 0x5c, 0x1b, + 0x20, 0x39, 0x81, 0xa7, 0xe5, 0x3d, 0x64, 0x8e, 0x14, 0xb3, 0x78, 0x95, 0x31, 0x1a, 0x3a, 0xa0, + 0xbc, 0x9f, 0x18, 0xe3, 0x25, 0xe2, 0xa4, 0x34, 0x91, 0x17, 0xaa, 0x71, 0xbe, 0xd1, 0xb0, 0x3d, + 0xb4, 0x46, 0xb6, 0xd7, 0x8a, 0xf2, 0x74, 0xa2, 0x65, 0x3c, 0x81, 0xa7, 0x31, 0xbd, 0xf1, 0x19, + 0xa7, 0x81, 0x60, 0xd7, 0xe8, 0x87, 0x45, 0xa6, 0x1a, 0xe2, 0xd8, 0x9a, 0x32, 0xa6, 0x37, 0x6f, + 0x8c, 0xed, 0x2b, 0x63, 0x22, 0x9f, 0x81, 0x53, 0x96, 0x11, 0x33, 0xce, 0xe2, 0x22, 0x5e, 0x6b, + 0xd4, 0x51, 0x61, 0x65, 0x99, 0x57, 0xda, 0x7c, 0x89, 0x78, 0x95, 0x53, 0xe1, 0x1e, 0x03, 0xf9, + 0x3e, 0x45, 0x6e, 0xda, 0xff, 0x5f, 0x93, 0x32, 0x81, 0xa3, 0x0d, 0x6f, 0x39, 0x21, 0x0e, 0x3c, + 0x14, 0x37, 0xfe, 0x82, 0xe6, 0x0b, 0xe3, 0x5c, 0x7e, 0x12, 0x17, 0xec, 0xa4, 0x10, 0x69, 0x21, + 0x7c, 0xc6, 0x43, 0xbc, 0x51, 0xd3, 0xd2, 0xf1, 0x36, 0x30, 0xf7, 0x18, 0xfa, 0x1e, 0xce, 0x59, + 0x2e, 0x30, 0x9b, 0xd0, 0x55, 0x8c, 0x5c, 0x94, 0x35, 0x10, 0x68, 0x4e, 0xa3, 0x64, 0xaa, 0xa6, + 0xc9, 0xf6, 0xd4, 0xd9, 0xed, 0x43, 0x6f, 0xc7, 0x3b, 0x8d, 0x56, 0xee, 0x5f, 0x16, 0x10, 0x03, + 0x54, 0x26, 0x98, 0xbc, 0x0f, 0x76, 0xaa, 0xd1, 0x75, 0x7d, 0xb6, 0xd7, 0x36, 0xd8, 0x37, 0xb2, + 0xc6, 0x57, 0x70, 0x58, 0xba, 0xe4, 0x18, 0x64, 0x28, 0x54, 0x95, 0xb6, 0xd7, 0x31, 0xe8, 0x5b, + 0x05, 0xca, 0xd1, 0x0c, 0x31, 0x17, 0x8c, 0xeb, 0x4e, 0xe8, 0x9a, 0xaa, 0x10, 0xf9, 0x04, 0x7a, + 0x8c, 0x07, 0x49, 0xcc, 0xf8, 0xdc, 0xa7, 0x71, 0x52, 0x70, 0xa1, 0xd5, 0xd7, 0xc3, 0x4e, 0x4a, + 0xdb, 0x99, 0x32, 0x49, 0xe9, 0x65, 0x44, 0x52, 0x88, 0x79, 0xb2, 0x1d, 0xb1, 0xaf, 0x23, 0x4a, + 0xdb, 0x3a, 0xc2, 0x7d, 0x09, 0xad, 0xd7, 0x3c, 0xc8, 0x56, 0xa9, 0xc0, 0x50, 0xea, 0x13, 0x52, + 0x41, 0xcd, 0xa5, 0xd4, 0xd9, 0xf5, 0xe0, 0xe0, 0x2d, 0x9b, 0xf3, 0x7a, 0xeb, 0xd6, 0xbb, 0xb5, + 0x6f, 0xdf, 0xed, 0x73, 0x68, 0xe5, 0x6c, 0xce, 0xa9, 0x28, 0x32, 0x34, 0x57, 0x5b, 0x03, 0xee, + 0xef, 0x0d, 0xe8, 0x5d, 0x2c, 0x30, 0x58, 0x9a, 0xae, 0xe7, 0x65, 0x83, 0x5e, 0xc1, 0x21, 0xea, + 0x6a, 0xfc, 0xca, 0xb0, 0xd8, 0x5e, 0xc7, 0xa0, 0x66, 0x9c, 0x7f, 0x80, 0xce, 0x8c, 0x2e, 0xd1, + 0x37, 0xf3, 0x97, 0x3b, 0x7b, 0xc3, 0xc6, 0xa8, 0x7d, 0x72, 0x3c, 0x56, 0xcb, 0xab, 0x8e, 0x79, + 0x7c, 0x49, 0x97, 0x58, 0x62, 0xaf, 0xb9, 0xc8, 0x56, 0x9e, 0x3d, 0xab, 0x40, 0xe4, 0x57, 0xe8, + 0xbf, 0xa3, 0x4c, 0x48, 0xe1, 0x82, 0x28, 0xc9, 0x2b, 0xdc, 0x0d, 0xc5, 0xfd, 0xe9, 0xbf, 0x70, + 0xff, 0xac, 0x03, 0x2f, 0x64, 0xdc, 0x66, 0x8e, 0xde, 0xbb, 0x1a, 0xd3, 0xe0, 0x4b, 0x78, 0xbc, + 0x53, 0x0e, 0x39, 0x82, 0xc6, 0xfa, 0x71, 0xc8, 0x23, 0xe9, 0xc1, 0xfe, 0x35, 0x8d, 0x0a, 0x54, + 0xd2, 0x36, 0x3d, 0xfd, 0xf1, 0xc5, 0xde, 0xe7, 0xd6, 0xe0, 0x6b, 0x78, 0x76, 0x67, 0xce, 0xfb, + 0x10, 0xb9, 0x7f, 0xee, 0x01, 0xd9, 0xba, 0x92, 0x7c, 0x7f, 0xbf, 0xc0, 0x63, 0x9e, 0x08, 0x7f, + 0x53, 0x63, 0x4b, 0xe9, 0xf0, 0x71, 0xad, 0x0e, 0x69, 0xb4, 0x1a, 0x7f, 0x97, 0x88, 0x5d, 0x91, + 0xbb, 0x7c, 0x13, 0x25, 0x3f, 0x42, 0x57, 0xe9, 0x1b, 0xfe, 0xbf, 0xe6, 0x49, 0x62, 0x75, 0xc7, + 0x70, 0x93, 0xf7, 0x30, 0xd8, 0x00, 0x07, 0xe7, 0xd0, 0xab, 0xcb, 0x7f, 0x2f, 0x55, 0xcf, 0xe0, + 0x49, 0x4d, 0xaa, 0xfb, 0x50, 0x9c, 0xfc, 0xb1, 0x07, 0x1d, 0x13, 0x2d, 0x97, 0x1a, 0x66, 0xe4, + 0x27, 0x29, 0xf0, 0xf6, 0xef, 0x20, 0x79, 0x59, 0x5e, 0xf6, 0x8e, 0xdf, 0xd5, 0xc1, 0x8b, 0xbb, + 0x1d, 0xe4, 0x72, 0x7a, 0x40, 0xce, 0xa0, 0x5d, 0x59, 0x9b, 0xc4, 0xd1, 0xfe, 0xbb, 0x7b, 0x77, + 0xd0, 0xaf, 0xb1, 0x68, 0x8a, 0x2b, 0xe8, 0x6e, 0x6d, 0x3e, 0xf2, 0x5c, 0x3b, 0xd7, 0xaf, 0xcf, + 0xc1, 0xe0, 0x0e, 0xab, 0xa6, 0x3b, 0x95, 0x57, 0xaf, 0x34, 0x8f, 0x74, 0xb5, 0xfb, 0xed, 0x7a, + 0x19, 0x6c, 0x03, 0xee, 0x83, 0xf3, 0x0f, 0xa0, 0xc7, 0x92, 0xf1, 0x3c, 0x4b, 0x03, 0x6d, 0xcb, + 0x31, 0xbb, 0x66, 0x01, 0x9e, 0xb7, 0xbe, 0xcd, 0xd3, 0x70, 0x22, 0xff, 0x85, 0x4c, 0xac, 0xe9, + 0x81, 0xfa, 0x3b, 0x72, 0xfa, 0x4f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x6e, 0x2c, 0x67, 0xf7, 0x9c, + 0x08, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. diff --git a/rpc/lspd.proto b/rpc/lspd.proto index 54032b6..450871d 100644 --- a/rpc/lspd.proto +++ b/rpc/lspd.proto @@ -52,6 +52,8 @@ message ChannelInformationReply { // The channel can be closed if not used this duration in seconds. int64 max_inactive_duration = 12; + + int64 channel_minimum_fee_msat = 13; } message OpenChannelRequest { diff --git a/server.go b/server.go index 6948874..23f7817 100644 --- a/server.go +++ b/server.go @@ -38,6 +38,7 @@ const ( feeRate = 0.000001 timeLockDelta = 144 channelFeePermyriad = 10 + channelMinimumFeeMsat = 0_000_000 additionalChannelCapacity = 100_000 maxInactiveDuration = 45 * 24 * 3600 ) @@ -57,18 +58,19 @@ var ( 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, - LspPubkey: publicKey.SerializeCompressed(), - MaxInactiveDuration: maxInactiveDuration, + 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 } From fda001f80a81ffcc63eafe3d5905b629f9e3be35 Mon Sep 17 00:00:00 2001 From: Yaacov Akiba Slama Date: Sun, 11 Jul 2021 13:56:19 +0300 Subject: [PATCH 058/214] Increase the fees to 0.4% and set min fees to 2000 sats --- server.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server.go b/server.go index 23f7817..ed7a796 100644 --- a/server.go +++ b/server.go @@ -37,8 +37,8 @@ const ( baseFeeMsat = 1000 feeRate = 0.000001 timeLockDelta = 144 - channelFeePermyriad = 10 - channelMinimumFeeMsat = 0_000_000 + channelFeePermyriad = 40 + channelMinimumFeeMsat = 2_000_000 additionalChannelCapacity = 100_000 maxInactiveDuration = 45 * 24 * 3600 ) From 7291d535ee7b4e2e9f40dee394a373dd73cee7da Mon Sep 17 00:00:00 2001 From: Yaacov Akiba Slama Date: Mon, 12 Jul 2021 11:13:08 +0300 Subject: [PATCH 059/214] Add the ability to handle client with zero channel reserve --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 16fe219..13dd83b 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,9 @@ You can create your own lsdp by implementing the grpc methods described [here](h You can apply the PR from https://github.com/lightningnetwork/lnd/pull/2708 to be able to create channels with a channel reserve smaller than 1% of the channel capacity. Then add the field `RemoteChanReserveSat` in the `lnrpc.OpenChannelRequest` struct when opening a channel. +In order to be able to let clients have a zero channel reserve, you can apply the +commit from https://github.com/breez/lnd/commit/03a7a0b6b4c8fa92ad94e9f449135e0738702643 + ## Flow for creating channels When Alice wants Bob to pay her an amount and Alice doesn't have a channel with sufficient capacity, she calls the lspd function RegisterPayment() and sending the paymentHash, paymentSecret (for mpp payments), destination (Alice pubkey), and two amounts. The first amount (incoming from the lsp point of view) is the amount BOB will pay. The second amount (outgoing from the lsp point of view) is the amount Alice will receive. The difference between these two amounts is the fees for the lsp. From ad0da518ed7513f0f7e1ba366463a907dfa86fec Mon Sep 17 00:00:00 2001 From: Yaacov Akiba Slama Date: Mon, 28 Feb 2022 22:21:49 +0200 Subject: [PATCH 060/214] Retrieve the channel amount and the channel privacy from the environment --- server.go | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/server.go b/server.go index ed7a796..b8ff662 100644 --- a/server.go +++ b/server.go @@ -9,6 +9,7 @@ import ( "log" "net" "os" + "strconv" "strings" lspdrpc "github.com/breez/lspd/rpc" @@ -115,13 +116,21 @@ func (s *server) OpenChannel(ctx context.Context, in *lspdrpc.OpenChannelRequest var txidStr string var outputIndex uint32 if len(nodeChannels) == 0 && len(pendingChannels) == 0 { + channelAmount, err := strconv.ParseInt(os.Getenv("CHANNEL_AMOUNT"), 0, 64) + if err != nil || channelAmount <= 0 { + channelAmount = publicChannelAmount + } + isPrivate, err := strconv.ParseBool(os.Getenv("CHANNEL_PRIVATE")) + if err != nil { + isPrivate = false + } response, err := client.OpenChannelSync(clientCtx, &lnrpc.OpenChannelRequest{ - LocalFundingAmount: publicChannelAmount, + LocalFundingAmount: channelAmount, NodePubkeyString: in.Pubkey, PushSat: 0, TargetConf: targetConf, MinHtlcMsat: minHtlcMsat, - Private: false, + Private: isPrivate, }) log.Printf("Response from OpenChannel: %#v (TX: %v)", response, hex.EncodeToString(response.GetFundingTxidBytes())) From 91d8730097edb5861c321c918ef2de06c768836a Mon Sep 17 00:00:00 2001 From: Yaacov Akiba Slama Date: Fri, 4 Mar 2022 07:57:53 +0200 Subject: [PATCH 061/214] Add MIT License --- LICENSE | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..6560904 --- /dev/null +++ b/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2022 Breez + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. From ad0595f3f9dc417dd2371215ec9c52ea2c66a801 Mon Sep 17 00:00:00 2001 From: Yaacov Akiba Slama Date: Fri, 29 Apr 2022 08:04:49 +0300 Subject: [PATCH 062/214] Log the amount and the privacy flag of the channel --- server.go | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/server.go b/server.go index b8ff662..15b770c 100644 --- a/server.go +++ b/server.go @@ -113,17 +113,21 @@ func (s *server) OpenChannel(ctx context.Context, in *lspdrpc.OpenChannelRequest 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 { - channelAmount, err := strconv.ParseInt(os.Getenv("CHANNEL_AMOUNT"), 0, 64) - if err != nil || channelAmount <= 0 { - channelAmount = publicChannelAmount - } - isPrivate, err := strconv.ParseBool(os.Getenv("CHANNEL_PRIVATE")) - if err != nil { - isPrivate = false - } response, err := client.OpenChannelSync(clientCtx, &lnrpc.OpenChannelRequest{ LocalFundingAmount: channelAmount, NodePubkeyString: in.Pubkey, From 5941d9b0ef2bec995df5b436e3e3007eeb7bb964 Mon Sep 17 00:00:00 2001 From: Yaacov Akiba Slama Date: Mon, 16 May 2022 11:51:50 +0300 Subject: [PATCH 063/214] Recover from lnd failure --- forwarding_history.go | 2 + intercept.go | 212 ++++++++++++++++++++++-------------------- 2 files changed, 111 insertions(+), 103 deletions(-) diff --git a/forwarding_history.go b/forwarding_history.go index ac35725..4a98caa 100644 --- a/forwarding_history.go +++ b/forwarding_history.go @@ -46,6 +46,8 @@ func channelsSynchronize(client chainrpc.ChainNotifierClient) { if err != nil { log.Printf("chainNotifierClient.RegisterBlockEpochNtfn(): %v", err) cancel() + time.Sleep(1 * time.Second) + continue } for { diff --git a/intercept.go b/intercept.go index 3e529f2..8f1cb4d 100644 --- a/intercept.go +++ b/intercept.go @@ -97,131 +97,137 @@ func getChannel(ctx context.Context, client lnrpc.LightningClient, node []byte, } func intercept() { - - clientCtx := metadata.AppendToOutgoingContext(context.Background(), "macaroon", os.Getenv("LND_MACAROON_HEX")) - interceptorClient, err := routerClient.HtlcInterceptor(clientCtx) - if err != nil { - log.Fatalf("routerClient.HtlcInterceptor(): %v", err) - } - for { - request, err := interceptorClient.Recv() + cancellableCtx, cancel := context.WithCancel(context.Background()) + clientCtx := metadata.AppendToOutgoingContext(cancellableCtx, "macaroon", os.Getenv("LND_MACAROON_HEX")) + interceptorClient, err := routerClient.HtlcInterceptor(clientCtx) if err != nil { - // If it is just the error result of the context cancellation - // the we exit silently. - status, ok := status.FromError(err) - if ok && status.Code() == codes.Canceled { + log.Printf("routerClient.HtlcInterceptor(): %v", err) + cancel() + time.Sleep(1 * time.Second) + continue + } + + for { + request, err := interceptorClient.Recv() + if err != nil { + // If it is just the error result of the context cancellation + // the we exit silently. + status, ok := status.FromError(err) + if ok && status.Code() == codes.Canceled { + break + } + // Otherwise it an unexpected error, we fail the test. + log.Printf("unexpected error in interceptor.Recv() %v", err) + cancel() break } - // Otherwise it an unexpected error, we fail the test. - log.Fatalf("unexpected error in interceptor.Recv() %v", err) - break - } - fmt.Printf("htlc: %v\nchanID: %v\nincoming amount: %v\noutgoing amount: %v\nincomin expiry: %v\noutgoing expiry: %v\npaymentHash: %x\nonionBlob: %x\n\n", - request.IncomingCircuitKey.HtlcId, - request.IncomingCircuitKey.ChanId, - request.IncomingAmountMsat, - request.OutgoingAmountMsat, - request.IncomingExpiry, - request.OutgoingExpiry, - request.PaymentHash, - request.OnionBlob, - ) + fmt.Printf("htlc: %v\nchanID: %v\nincoming amount: %v\noutgoing amount: %v\nincomin expiry: %v\noutgoing expiry: %v\npaymentHash: %x\nonionBlob: %x\n\n", + request.IncomingCircuitKey.HtlcId, + request.IncomingCircuitKey.ChanId, + request.IncomingAmountMsat, + request.OutgoingAmountMsat, + request.IncomingExpiry, + request.OutgoingExpiry, + request.PaymentHash, + request.OnionBlob, + ) - paymentHash, paymentSecret, destination, incomingAmountMsat, outgoingAmountMsat, fundingTxID, fundingTxOutnum, err := paymentInfo(request.PaymentHash) - if err != nil { - log.Printf("paymentInfo(%x) error: %v", request.PaymentHash, err) - } - log.Printf("paymentHash:%x\npaymentSecret:%x\ndestination:%x\nincomingAmountMsat:%v\noutgoingAmountMsat:%v\n\n", - paymentHash, paymentSecret, destination, incomingAmountMsat, outgoingAmountMsat) - if paymentSecret != nil { + paymentHash, paymentSecret, destination, incomingAmountMsat, outgoingAmountMsat, fundingTxID, fundingTxOutnum, err := paymentInfo(request.PaymentHash) + if err != nil { + log.Printf("paymentInfo(%x) error: %v", request.PaymentHash, err) + } + log.Printf("paymentHash:%x\npaymentSecret:%x\ndestination:%x\nincomingAmountMsat:%v\noutgoingAmountMsat:%v\n\n", + paymentHash, paymentSecret, destination, incomingAmountMsat, outgoingAmountMsat) + if paymentSecret != nil { - if fundingTxID == nil { - if bytes.Compare(paymentHash, request.PaymentHash) == 0 { - fundingTxID, fundingTxOutnum, err = openChannel(clientCtx, client, request.PaymentHash, destination, incomingAmountMsat) - log.Printf("openChannel(%x, %v) err: %v", destination, incomingAmountMsat, err) - if err != nil { + if fundingTxID == nil { + if bytes.Compare(paymentHash, request.PaymentHash) == 0 { + fundingTxID, fundingTxOutnum, err = openChannel(clientCtx, client, request.PaymentHash, destination, incomingAmountMsat) + log.Printf("openChannel(%x, %v) err: %v", destination, incomingAmountMsat, err) + if err != nil { + interceptorClient.Send(&routerrpc.ForwardHtlcInterceptResponse{ + IncomingCircuitKey: request.IncomingCircuitKey, + Action: routerrpc.ResolveHoldForwardAction_FAIL, + }) + continue + } + } else { //probing + failureCode := routerrpc.ForwardHtlcInterceptResponse_TEMPORARY_CHANNEL_FAILURE + if err := isConnected(clientCtx, client, destination); err == nil { + failureCode = routerrpc.ForwardHtlcInterceptResponse_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS + } interceptorClient.Send(&routerrpc.ForwardHtlcInterceptResponse{ IncomingCircuitKey: request.IncomingCircuitKey, Action: routerrpc.ResolveHoldForwardAction_FAIL, + FailureCode: failureCode, }) continue } - } else { //probing - failureCode := routerrpc.ForwardHtlcInterceptResponse_TEMPORARY_CHANNEL_FAILURE - if err := isConnected(clientCtx, client, destination); err == nil { - failureCode = routerrpc.ForwardHtlcInterceptResponse_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS - } + } + + pubKey, err := btcec.ParsePubKey(destination, btcec.S256()) + log.Printf("btcec.ParsePubKey(%x): %v", destination, err) + + sessionKey, err := btcec.NewPrivateKey(btcec.S256()) + log.Printf("btcec.NewPrivateKey(): %v", err) + + var bigProd, bigAmt big.Int + amt := (bigAmt.Div(bigProd.Mul(big.NewInt(outgoingAmountMsat), big.NewInt(int64(request.OutgoingAmountMsat))), big.NewInt(incomingAmountMsat))).Int64() + + var addr [32]byte + copy(addr[:], paymentSecret) + hop := route.Hop{ + AmtToForward: lnwire.MilliSatoshi(amt), + OutgoingTimeLock: request.OutgoingExpiry, + MPP: record.NewMPP(lnwire.MilliSatoshi(outgoingAmountMsat), addr), + CustomRecords: make(record.CustomSet), + } + + var b bytes.Buffer + err = hop.PackHopPayload(&b, uint64(0)) + log.Printf("hop.PackHopPayload(): %v", err) + + payload, err := sphinx.NewHopPayload(nil, b.Bytes()) + log.Printf("sphinx.NewHopPayload(): %v", err) + + var sphinxPath sphinx.PaymentPath + sphinxPath[0] = sphinx.OnionHop{ + NodePub: *pubKey, + HopPayload: payload, + } + sphinxPacket, err := sphinx.NewOnionPacket( + &sphinxPath, sessionKey, request.PaymentHash, + sphinx.DeterministicPacketFiller, + ) + var onionBlob bytes.Buffer + err = sphinxPacket.Encode(&onionBlob) + log.Printf("sphinxPacket.Encode(): %v", err) + var h chainhash.Hash + err = h.SetBytes(fundingTxID) + if err != nil { + log.Printf("h.SetBytes(%x) error: %v", fundingTxID, err) interceptorClient.Send(&routerrpc.ForwardHtlcInterceptResponse{ IncomingCircuitKey: request.IncomingCircuitKey, Action: routerrpc.ResolveHoldForwardAction_FAIL, - FailureCode: failureCode, }) continue } - } + channelPoint := wire.NewOutPoint(&h, fundingTxOutnum).String() + go resumeOrCancel( + clientCtx, interceptorClient, request.IncomingCircuitKey, destination, + channelPoint, uint64(amt), onionBlob.Bytes(), + ) - pubKey, err := btcec.ParsePubKey(destination, btcec.S256()) - log.Printf("btcec.ParsePubKey(%x): %v", destination, err) - - sessionKey, err := btcec.NewPrivateKey(btcec.S256()) - log.Printf("btcec.NewPrivateKey(): %v", err) - - var bigProd, bigAmt big.Int - amt := (bigAmt.Div(bigProd.Mul(big.NewInt(outgoingAmountMsat), big.NewInt(int64(request.OutgoingAmountMsat))), big.NewInt(incomingAmountMsat))).Int64() - - var addr [32]byte - copy(addr[:], paymentSecret) - hop := route.Hop{ - AmtToForward: lnwire.MilliSatoshi(amt), - OutgoingTimeLock: request.OutgoingExpiry, - MPP: record.NewMPP(lnwire.MilliSatoshi(outgoingAmountMsat), addr), - CustomRecords: make(record.CustomSet), - } - - var b bytes.Buffer - err = hop.PackHopPayload(&b, uint64(0)) - log.Printf("hop.PackHopPayload(): %v", err) - - payload, err := sphinx.NewHopPayload(nil, b.Bytes()) - log.Printf("sphinx.NewHopPayload(): %v", err) - - var sphinxPath sphinx.PaymentPath - sphinxPath[0] = sphinx.OnionHop{ - NodePub: *pubKey, - HopPayload: payload, - } - sphinxPacket, err := sphinx.NewOnionPacket( - &sphinxPath, sessionKey, request.PaymentHash, - sphinx.DeterministicPacketFiller, - ) - var onionBlob bytes.Buffer - err = sphinxPacket.Encode(&onionBlob) - log.Printf("sphinxPacket.Encode(): %v", err) - var h chainhash.Hash - err = h.SetBytes(fundingTxID) - if err != nil { - log.Printf("h.SetBytes(%x) error: %v", fundingTxID, err) + } else { interceptorClient.Send(&routerrpc.ForwardHtlcInterceptResponse{ - IncomingCircuitKey: request.IncomingCircuitKey, - Action: routerrpc.ResolveHoldForwardAction_FAIL, + IncomingCircuitKey: request.IncomingCircuitKey, + Action: routerrpc.ResolveHoldForwardAction_RESUME, + OutgoingAmountMsat: request.OutgoingAmountMsat, + OutgoingRequestedChanId: request.OutgoingRequestedChanId, + OnionBlob: request.OnionBlob, }) - continue } - channelPoint := wire.NewOutPoint(&h, fundingTxOutnum).String() - go resumeOrCancel( - clientCtx, interceptorClient, request.IncomingCircuitKey, destination, - channelPoint, uint64(amt), onionBlob.Bytes(), - ) - - } else { - interceptorClient.Send(&routerrpc.ForwardHtlcInterceptResponse{ - IncomingCircuitKey: request.IncomingCircuitKey, - Action: routerrpc.ResolveHoldForwardAction_RESUME, - OutgoingAmountMsat: request.OutgoingAmountMsat, - OutgoingRequestedChanId: request.OutgoingRequestedChanId, - OnionBlob: request.OnionBlob, - }) } } } From bed7551652ee69bb9758414080e9517616131423 Mon Sep 17 00:00:00 2001 From: ueno Date: Sun, 26 Jun 2022 00:10:36 +0900 Subject: [PATCH 064/214] go.mod: tidy --- .gitignore | 2 ++ go.mod | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7a25686 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +go.sum +lspd diff --git a/go.mod b/go.mod index f3f10eb..7573a3a 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,6 @@ require ( github.com/lightningnetwork/lnd v0.11.0-beta golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e google.golang.org/grpc v1.29.1 - google.golang.org/protobuf v1.23.0 ) replace github.com/lightningnetwork/lnd v0.11.0-beta => github.com/breez/lnd v0.11.0-beta.rc4.0.20210125150416-0c10146b223c From 5457b04a1cf4dd58179d08074643c4383b51422d Mon Sep 17 00:00:00 2001 From: ueno Date: Sun, 26 Jun 2022 00:12:50 +0900 Subject: [PATCH 065/214] interceptor fail --- intercept.go | 59 +++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 42 insertions(+), 17 deletions(-) diff --git a/intercept.go b/intercept.go index 8f1cb4d..3a912c8 100644 --- a/intercept.go +++ b/intercept.go @@ -96,6 +96,13 @@ func getChannel(ctx context.Context, client lnrpc.LightningClient, node []byte, return 0 } +func failForwardSend(interceptorClient routerrpc.Router_HtlcInterceptorClient, incomingCircuitKey *routerrpc.CircuitKey) { + interceptorClient.Send(&routerrpc.ForwardHtlcInterceptResponse{ + IncomingCircuitKey: incomingCircuitKey, + Action: routerrpc.ResolveHoldForwardAction_FAIL, + }) +} + func intercept() { for { cancellableCtx, cancel := context.WithCancel(context.Background()) @@ -136,6 +143,8 @@ func intercept() { paymentHash, paymentSecret, destination, incomingAmountMsat, outgoingAmountMsat, fundingTxID, fundingTxOutnum, err := paymentInfo(request.PaymentHash) if err != nil { log.Printf("paymentInfo(%x) error: %v", request.PaymentHash, err) + failForwardSend(interceptorClient, request.IncomingCircuitKey) + continue } log.Printf("paymentHash:%x\npaymentSecret:%x\ndestination:%x\nincomingAmountMsat:%v\noutgoingAmountMsat:%v\n\n", paymentHash, paymentSecret, destination, incomingAmountMsat, outgoingAmountMsat) @@ -146,10 +155,7 @@ func intercept() { fundingTxID, fundingTxOutnum, err = openChannel(clientCtx, client, request.PaymentHash, destination, incomingAmountMsat) log.Printf("openChannel(%x, %v) err: %v", destination, incomingAmountMsat, err) if err != nil { - interceptorClient.Send(&routerrpc.ForwardHtlcInterceptResponse{ - IncomingCircuitKey: request.IncomingCircuitKey, - Action: routerrpc.ResolveHoldForwardAction_FAIL, - }) + failForwardSend(interceptorClient, request.IncomingCircuitKey) continue } } else { //probing @@ -167,10 +173,18 @@ func intercept() { } pubKey, err := btcec.ParsePubKey(destination, btcec.S256()) - log.Printf("btcec.ParsePubKey(%x): %v", destination, err) + if err != nil { + log.Printf("btcec.ParsePubKey(%x): %v", destination, err) + failForwardSend(interceptorClient, request.IncomingCircuitKey) + continue + } sessionKey, err := btcec.NewPrivateKey(btcec.S256()) - log.Printf("btcec.NewPrivateKey(): %v", err) + if err != nil { + log.Printf("btcec.NewPrivateKey(): %v", err) + failForwardSend(interceptorClient, request.IncomingCircuitKey) + continue + } var bigProd, bigAmt big.Int amt := (bigAmt.Div(bigProd.Mul(big.NewInt(outgoingAmountMsat), big.NewInt(int64(request.OutgoingAmountMsat))), big.NewInt(incomingAmountMsat))).Int64() @@ -186,10 +200,18 @@ func intercept() { var b bytes.Buffer err = hop.PackHopPayload(&b, uint64(0)) - log.Printf("hop.PackHopPayload(): %v", err) + if err != nil { + log.Printf("hop.PackHopPayload(): %v", err) + failForwardSend(interceptorClient, request.IncomingCircuitKey) + continue + } payload, err := sphinx.NewHopPayload(nil, b.Bytes()) - log.Printf("sphinx.NewHopPayload(): %v", err) + if err != nil { + log.Printf("sphinx.NewHopPayload(): %v", err) + failForwardSend(interceptorClient, request.IncomingCircuitKey) + continue + } var sphinxPath sphinx.PaymentPath sphinxPath[0] = sphinx.OnionHop{ @@ -200,17 +222,23 @@ func intercept() { &sphinxPath, sessionKey, request.PaymentHash, sphinx.DeterministicPacketFiller, ) + if err != nil { + log.Printf("sphinx.NewOnionPacket(): %v", err) + failForwardSend(interceptorClient, request.IncomingCircuitKey) + continue + } var onionBlob bytes.Buffer err = sphinxPacket.Encode(&onionBlob) - log.Printf("sphinxPacket.Encode(): %v", err) + if err != nil { + log.Printf("sphinxPacket.Encode(): %v", err) + failForwardSend(interceptorClient, request.IncomingCircuitKey) + continue + } var h chainhash.Hash err = h.SetBytes(fundingTxID) if err != nil { log.Printf("h.SetBytes(%x) error: %v", fundingTxID, err) - interceptorClient.Send(&routerrpc.ForwardHtlcInterceptResponse{ - IncomingCircuitKey: request.IncomingCircuitKey, - Action: routerrpc.ResolveHoldForwardAction_FAIL, - }) + failForwardSend(interceptorClient, request.IncomingCircuitKey) continue } channelPoint := wire.NewOutPoint(&h, fundingTxOutnum).String() @@ -265,8 +293,5 @@ func resumeOrCancel( } time.Sleep(1 * time.Second) } - interceptorClient.Send(&routerrpc.ForwardHtlcInterceptResponse{ - IncomingCircuitKey: incomingCircuitKey, - Action: routerrpc.ResolveHoldForwardAction_FAIL, - }) + failForwardSend(interceptorClient, incomingCircuitKey) } From 89212aa5ae59d85964af18f662e652652f1a3411 Mon Sep 17 00:00:00 2001 From: Yaacov Akiba Slama Date: Sun, 17 Jul 2022 18:05:58 +0300 Subject: [PATCH 066/214] Update lnd (and corresponding btcd) --- btceclegacy/ciphering.go | 211 +++++++++++++++++++++++++++++++++++++++ go.mod | 26 ++--- intercept.go | 10 +- server.go | 17 ++-- 4 files changed, 238 insertions(+), 26 deletions(-) create mode 100644 btceclegacy/ciphering.go diff --git a/btceclegacy/ciphering.go b/btceclegacy/ciphering.go new file mode 100644 index 0000000..ee2260e --- /dev/null +++ b/btceclegacy/ciphering.go @@ -0,0 +1,211 @@ +// Copyright (c) 2015-2016 The btcsuite developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package btceclegacy + +import ( + "bytes" + "crypto/aes" + "crypto/cipher" + "crypto/hmac" + "crypto/rand" + "crypto/sha256" + "crypto/sha512" + "errors" + "io" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/decred/dcrd/dcrec/secp256k1/v4" +) + +var ( + // ErrInvalidMAC occurs when Message Authentication Check (MAC) fails + // during decryption. This happens because of either invalid private key or + // corrupt ciphertext. + ErrInvalidMAC = errors.New("invalid mac hash") + + // errInputTooShort occurs when the input ciphertext to the Decrypt + // function is less than 134 bytes long. + errInputTooShort = errors.New("ciphertext too short") + + // errUnsupportedCurve occurs when the first two bytes of the encrypted + // text aren't 0x02CA (= 712 = secp256k1, from OpenSSL). + errUnsupportedCurve = errors.New("unsupported curve") + + errInvalidXLength = errors.New("invalid X length, must be 32") + errInvalidYLength = errors.New("invalid Y length, must be 32") + errInvalidPadding = errors.New("invalid PKCS#7 padding") + + // 0x02CA = 714 + ciphCurveBytes = [2]byte{0x02, 0xCA} + // 0x20 = 32 + ciphCoordLength = [2]byte{0x00, 0x20} +) + +// Encrypt encrypts data for the target public key using AES-256-CBC. It also +// generates a private key (the pubkey of which is also in the output). The only +// supported curve is secp256k1. The `structure' that it encodes everything into +// is: +// +// struct { +// // Initialization Vector used for AES-256-CBC +// IV [16]byte +// // Public Key: curve(2) + len_of_pubkeyX(2) + pubkeyX + +// // len_of_pubkeyY(2) + pubkeyY (curve = 714) +// PublicKey [70]byte +// // Cipher text +// Data []byte +// // HMAC-SHA-256 Message Authentication Code +// HMAC [32]byte +// } +// +// The primary aim is to ensure byte compatibility with Pyelliptic. Also, refer +// to section 5.8.1 of ANSI X9.63 for rationale on this format. +func Encrypt(pubkey *btcec.PublicKey, in []byte) ([]byte, error) { + ephemeral, err := btcec.NewPrivateKey() + if err != nil { + return nil, err + } + ecdhKey := secp256k1.GenerateSharedSecret(ephemeral, pubkey) + derivedKey := sha512.Sum512(ecdhKey) + keyE := derivedKey[:32] + keyM := derivedKey[32:] + + paddedIn := addPKCSPadding(in) + // IV + Curve params/X/Y + padded plaintext/ciphertext + HMAC-256 + out := make([]byte, aes.BlockSize+70+len(paddedIn)+sha256.Size) + iv := out[:aes.BlockSize] + if _, err = io.ReadFull(rand.Reader, iv); err != nil { + return nil, err + } + // start writing public key + pb := ephemeral.PubKey().SerializeUncompressed() + offset := aes.BlockSize + + // curve and X length + copy(out[offset:offset+4], append(ciphCurveBytes[:], ciphCoordLength[:]...)) + offset += 4 + // X + copy(out[offset:offset+32], pb[1:33]) + offset += 32 + // Y length + copy(out[offset:offset+2], ciphCoordLength[:]) + offset += 2 + // Y + copy(out[offset:offset+32], pb[33:]) + offset += 32 + + // start encryption + block, err := aes.NewCipher(keyE) + if err != nil { + return nil, err + } + mode := cipher.NewCBCEncrypter(block, iv) + mode.CryptBlocks(out[offset:len(out)-sha256.Size], paddedIn) + + // start HMAC-SHA-256 + hm := hmac.New(sha256.New, keyM) + hm.Write(out[:len(out)-sha256.Size]) // everything is hashed + copy(out[len(out)-sha256.Size:], hm.Sum(nil)) // write checksum + + return out, nil +} + +// Decrypt decrypts data that was encrypted using the Encrypt function. +func Decrypt(priv *btcec.PrivateKey, in []byte) ([]byte, error) { + // IV + Curve params/X/Y + 1 block + HMAC-256 + if len(in) < aes.BlockSize+70+aes.BlockSize+sha256.Size { + return nil, errInputTooShort + } + + // read iv + iv := in[:aes.BlockSize] + offset := aes.BlockSize + + // start reading pubkey + if !bytes.Equal(in[offset:offset+2], ciphCurveBytes[:]) { + return nil, errUnsupportedCurve + } + offset += 2 + + if !bytes.Equal(in[offset:offset+2], ciphCoordLength[:]) { + return nil, errInvalidXLength + } + offset += 2 + + xBytes := in[offset : offset+32] + offset += 32 + + if !bytes.Equal(in[offset:offset+2], ciphCoordLength[:]) { + return nil, errInvalidYLength + } + offset += 2 + + yBytes := in[offset : offset+32] + offset += 32 + + pb := make([]byte, 65) + pb[0] = byte(0x04) // uncompressed + copy(pb[1:33], xBytes) + copy(pb[33:], yBytes) + // check if (X, Y) lies on the curve and create a Pubkey if it does + pubkey, err := btcec.ParsePubKey(pb) + if err != nil { + return nil, err + } + + // check for cipher text length + if (len(in)-aes.BlockSize-offset-sha256.Size)%aes.BlockSize != 0 { + return nil, errInvalidPadding // not padded to 16 bytes + } + + // read hmac + messageMAC := in[len(in)-sha256.Size:] + + // generate shared secret + ecdhKey := secp256k1.GenerateSharedSecret(priv, pubkey) + derivedKey := sha512.Sum512(ecdhKey) + keyE := derivedKey[:32] + keyM := derivedKey[32:] + + // verify mac + hm := hmac.New(sha256.New, keyM) + hm.Write(in[:len(in)-sha256.Size]) // everything is hashed + expectedMAC := hm.Sum(nil) + if !hmac.Equal(messageMAC, expectedMAC) { + return nil, ErrInvalidMAC + } + + // start decryption + block, err := aes.NewCipher(keyE) + if err != nil { + return nil, err + } + mode := cipher.NewCBCDecrypter(block, iv) + // same length as ciphertext + plaintext := make([]byte, len(in)-offset-sha256.Size) + mode.CryptBlocks(plaintext, in[offset:len(in)-sha256.Size]) + + return removePKCSPadding(plaintext) +} + +// Implement PKCS#7 padding with block size of 16 (AES block size). + +// addPKCSPadding adds padding to a block of data +func addPKCSPadding(src []byte) []byte { + padding := aes.BlockSize - len(src)%aes.BlockSize + padtext := bytes.Repeat([]byte{byte(padding)}, padding) + return append(src, padtext...) +} + +// removePKCSPadding removes padding from data that was added with addPKCSPadding +func removePKCSPadding(src []byte) ([]byte, error) { + length := len(src) + padLength := int(src[length-1]) + if padLength > aes.BlockSize || length < aes.BlockSize { + return nil, errInvalidPadding + } + + return src[:length-padLength], nil +} diff --git a/go.mod b/go.mod index 7573a3a..9a89112 100644 --- a/go.mod +++ b/go.mod @@ -4,19 +4,19 @@ go 1.14 require ( github.com/aws/aws-sdk-go v1.30.20 - github.com/btcsuite/btcd v0.20.1-beta.0.20200730232343-1db1b6f8217f + github.com/btcsuite/btcd v0.23.1 + github.com/btcsuite/btcd/btcec/v2 v2.2.0 + github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 github.com/caddyserver/certmagic v0.11.2 - github.com/coreos/etcd v3.3.25+incompatible // indirect - github.com/coreos/go-semver v0.3.0 // indirect - github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f // indirect - github.com/golang/protobuf v1.4.2 - github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 - github.com/jackc/pgtype v1.4.2 - github.com/jackc/pgx/v4 v4.8.1 - github.com/lightningnetwork/lightning-onion v1.0.2-0.20200501022730-3c8c8d0b89ea - github.com/lightningnetwork/lnd v0.11.0-beta - golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e - google.golang.org/grpc v1.29.1 + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 + github.com/golang/protobuf v1.5.2 + github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 + github.com/jackc/pgtype v1.8.1 + github.com/jackc/pgx/v4 v4.13.0 + github.com/lightningnetwork/lightning-onion v1.0.2-0.20220211021909-bb84a1ccb0c5 + github.com/lightningnetwork/lnd v0.15.0-beta + golang.org/x/sync v0.0.0-20210220032951-036812b2e83c + google.golang.org/grpc v1.38.0 ) -replace github.com/lightningnetwork/lnd v0.11.0-beta => github.com/breez/lnd v0.11.0-beta.rc4.0.20210125150416-0c10146b223c +replace github.com/lightningnetwork/lnd v0.15.0-beta => github.com/breez/lnd v0.15.0-beta.rc6.0.20220715110145-7f7cfa410adc diff --git a/intercept.go b/intercept.go index 3a912c8..1f4ca2a 100644 --- a/intercept.go +++ b/intercept.go @@ -10,7 +10,7 @@ import ( "os" "time" - "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/lnrpc" @@ -159,9 +159,9 @@ func intercept() { continue } } else { //probing - failureCode := routerrpc.ForwardHtlcInterceptResponse_TEMPORARY_CHANNEL_FAILURE + failureCode := lnrpc.Failure_TEMPORARY_CHANNEL_FAILURE if err := isConnected(clientCtx, client, destination); err == nil { - failureCode = routerrpc.ForwardHtlcInterceptResponse_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS + failureCode = lnrpc.Failure_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS } interceptorClient.Send(&routerrpc.ForwardHtlcInterceptResponse{ IncomingCircuitKey: request.IncomingCircuitKey, @@ -172,14 +172,14 @@ func intercept() { } } - pubKey, err := btcec.ParsePubKey(destination, btcec.S256()) + pubKey, err := btcec.ParsePubKey(destination) if err != nil { log.Printf("btcec.ParsePubKey(%x): %v", destination, err) failForwardSend(interceptorClient, request.IncomingCircuitKey) continue } - sessionKey, err := btcec.NewPrivateKey(btcec.S256()) + sessionKey, err := btcec.NewPrivateKey() if err != nil { log.Printf("btcec.NewPrivateKey(): %v", err) failForwardSend(interceptorClient, request.IncomingCircuitKey) diff --git a/server.go b/server.go index 15b770c..88dca73 100644 --- a/server.go +++ b/server.go @@ -12,10 +12,11 @@ import ( "strconv" "strings" + "github.com/breez/lspd/btceclegacy" lspdrpc "github.com/breez/lspd/rpc" "github.com/golang/protobuf/proto" - "github.com/btcsuite/btcd/btcec" + "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" @@ -76,7 +77,7 @@ func (s *server) ChannelInformation(ctx context.Context, in *lspdrpc.ChannelInfo } func (s *server) RegisterPayment(ctx context.Context, in *lspdrpc.RegisterPaymentRequest) (*lspdrpc.RegisterPaymentReply, error) { - data, err := btcec.Decrypt(privateKey, in.Blob) + 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) @@ -161,7 +162,7 @@ func (s *server) OpenChannel(ctx context.Context, in *lspdrpc.OpenChannelRequest } func getSignedEncryptedData(in *lspdrpc.Encrypted) (string, []byte, error) { - signedBlob, err := btcec.Decrypt(privateKey, in.Data) + 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) @@ -172,7 +173,7 @@ func getSignedEncryptedData(in *lspdrpc.Encrypted) (string, []byte, error) { 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, btcec.S256()) + 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) @@ -225,12 +226,12 @@ func (s *server) CheckChannels(ctx context.Context, in *lspdrpc.Encrypted) (*lsp log.Printf("proto.Marshall() error: %v", err) return nil, fmt.Errorf("proto.Marshal() error: %w", err) } - pubkey, err := btcec.ParsePubKey(checkChannelsRequest.EncryptPubkey, btcec.S256()) + 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 := btcec.Encrypt(pubkey, dataReply) + 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) @@ -323,7 +324,7 @@ func getPendingNodeChannels(nodeID string) ([]*lnrpc.PendingChannelsResponse_Pen func main() { if len(os.Args) > 1 && os.Args[1] == "genkey" { - p, err := btcec.NewPrivateKey(btcec.S256()) + p, err := btcec.NewPrivateKey() if err != nil { log.Fatalf("btcec.NewPrivateKey() error: %v", err) } @@ -340,7 +341,7 @@ func main() { 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(btcec.S256(), privateKeyBytes) + privateKey, publicKey = btcec.PrivKeyFromBytes(privateKeyBytes) certmagicDomain := os.Getenv("CERTMAGIC_DOMAIN") address := os.Getenv("LISTEN_ADDRESS") From 1d6e5ca99fa256462f4881c3d8df597e1231de1a Mon Sep 17 00:00:00 2001 From: Yaacov Akiba Slama Date: Sun, 18 Sep 2022 21:34:59 +0300 Subject: [PATCH 067/214] Update lnd to 15.1 --- go.mod | 142 +++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 138 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index 9a89112..9860553 100644 --- a/go.mod +++ b/go.mod @@ -1,11 +1,11 @@ module github.com/breez/lspd -go 1.14 +go 1.19 require ( github.com/aws/aws-sdk-go v1.30.20 github.com/btcsuite/btcd v0.23.1 - github.com/btcsuite/btcd/btcec/v2 v2.2.0 + github.com/btcsuite/btcd/btcec/v2 v2.2.1 github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 github.com/caddyserver/certmagic v0.11.2 github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 @@ -14,9 +14,143 @@ require ( github.com/jackc/pgtype v1.8.1 github.com/jackc/pgx/v4 v4.13.0 github.com/lightningnetwork/lightning-onion v1.0.2-0.20220211021909-bb84a1ccb0c5 - github.com/lightningnetwork/lnd v0.15.0-beta + github.com/lightningnetwork/lnd v0.15.1-beta golang.org/x/sync v0.0.0-20210220032951-036812b2e83c google.golang.org/grpc v1.38.0 ) -replace github.com/lightningnetwork/lnd v0.15.0-beta => github.com/breez/lnd v0.15.0-beta.rc6.0.20220715110145-7f7cfa410adc +require ( + github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect + github.com/aead/siphash v1.0.1 // indirect + github.com/andybalholm/brotli v1.0.3 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/btcsuite/btcd/btcutil v1.1.2 // indirect + github.com/btcsuite/btcd/btcutil/psbt v1.1.5 // indirect + github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f // indirect + github.com/btcsuite/btcwallet v0.15.1 // indirect + github.com/btcsuite/btcwallet/wallet/txauthor v1.2.3 // indirect + github.com/btcsuite/btcwallet/wallet/txrules v1.2.0 // indirect + github.com/btcsuite/btcwallet/wallet/txsizes v1.1.0 // indirect + github.com/btcsuite/btcwallet/walletdb v1.4.0 // indirect + github.com/btcsuite/btcwallet/wtxmgr v1.5.0 // indirect + github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd // indirect + github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 // indirect + github.com/btcsuite/winsvc v1.0.0 // indirect + github.com/cenkalti/backoff/v4 v4.0.0 // indirect + github.com/cespare/xxhash/v2 v2.1.1 // indirect + github.com/coreos/go-semver v0.3.0 // indirect + github.com/coreos/go-systemd/v22 v22.3.2 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/decred/dcrd/crypto/blake256 v1.0.0 // indirect + github.com/decred/dcrd/lru v1.0.0 // indirect + github.com/dsnet/compress v0.0.1 // indirect + github.com/dustin/go-humanize v1.0.0 // indirect + github.com/dvyukov/go-fuzz v0.0.0-20210602112143-b1f3d6f4ef4e // indirect + github.com/fergusstrange/embedded-postgres v1.10.0 // indirect + github.com/form3tech-oss/jwt-go v3.2.3+incompatible // indirect + github.com/go-acme/lego/v3 v3.7.0 // indirect + github.com/go-errors/errors v1.0.1 // indirect + github.com/gofrs/uuid v4.2.0+incompatible // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/snappy v0.0.4 // indirect + github.com/google/btree v1.0.1 // indirect + github.com/gorilla/websocket v1.4.2 // indirect + github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect + github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.5.0 // indirect + github.com/jackc/chunkreader/v2 v2.0.1 // indirect + github.com/jackc/pgconn v1.10.0 // indirect + github.com/jackc/pgio v1.0.0 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgproto3/v2 v2.1.1 // indirect + github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect + github.com/jackc/puddle v1.1.3 // indirect + github.com/jessevdk/go-flags v1.4.0 // indirect + github.com/jmespath/go-jmespath v0.3.0 // indirect + github.com/jonboulle/clockwork v0.2.2 // indirect + github.com/jrick/logrotate v1.0.0 // indirect + github.com/json-iterator/go v1.1.11 // indirect + github.com/juju/clock v1.0.2 // indirect + github.com/juju/errors v1.0.0 // indirect + github.com/juju/loggo v1.0.0 // indirect + github.com/juju/testing v1.0.1 // indirect + github.com/kkdai/bstream v1.0.0 // indirect + github.com/klauspost/compress v1.13.6 // indirect + github.com/klauspost/cpuid v1.2.3 // indirect + github.com/klauspost/pgzip v1.2.5 // indirect + github.com/kr/pretty v0.3.0 // indirect + github.com/lib/pq v1.10.3 // indirect + github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf // indirect + github.com/lightninglabs/neutrino v0.14.2 // indirect + github.com/lightningnetwork/lnd/clock v1.1.0 // indirect + github.com/lightningnetwork/lnd/healthcheck v1.2.2 // indirect + github.com/lightningnetwork/lnd/kvdb v1.3.1 // indirect + github.com/lightningnetwork/lnd/queue v1.1.0 // indirect + github.com/lightningnetwork/lnd/ticker v1.1.0 // indirect + github.com/lightningnetwork/lnd/tlv v1.0.3 // indirect + github.com/lightningnetwork/lnd/tor v1.0.1 // indirect + github.com/ltcsuite/ltcd v0.0.0-20190101042124-f37f8bf35796 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect + github.com/mholt/archiver/v3 v3.5.0 // indirect + github.com/miekg/dns v1.1.43 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.1 // indirect + github.com/nwaples/rardecode v1.1.2 // indirect + github.com/pierrec/lz4/v4 v4.1.8 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/prometheus/client_golang v1.11.0 // indirect + github.com/prometheus/client_model v0.2.0 // indirect + github.com/prometheus/common v0.26.0 // indirect + github.com/prometheus/procfs v0.6.0 // indirect + github.com/rogpeppe/fastuuid v1.2.0 // indirect + github.com/rogpeppe/go-internal v1.8.1 // indirect + github.com/sirupsen/logrus v1.7.0 // indirect + github.com/soheilhy/cmux v0.1.5 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/stretchr/objx v0.2.0 // indirect + github.com/stretchr/testify v1.7.1 // indirect + github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect + github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802 // indirect + github.com/ulikunitz/xz v0.5.10 // indirect + github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect + github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect + go.etcd.io/bbolt v1.3.6 // indirect + go.etcd.io/etcd/api/v3 v3.5.0 // indirect + go.etcd.io/etcd/client/pkg/v3 v3.5.0 // indirect + go.etcd.io/etcd/client/v2 v2.305.0 // indirect + go.etcd.io/etcd/client/v3 v3.5.0 // indirect + go.etcd.io/etcd/pkg/v3 v3.5.0 // indirect + go.etcd.io/etcd/raft/v3 v3.5.0 // indirect + go.etcd.io/etcd/server/v3 v3.5.0 // indirect + go.opentelemetry.io/contrib v0.20.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.20.0 // indirect + go.opentelemetry.io/otel v0.20.0 // indirect + go.opentelemetry.io/otel/exporters/otlp v0.20.0 // indirect + go.opentelemetry.io/otel/metric v0.20.0 // indirect + go.opentelemetry.io/otel/sdk v0.20.0 // indirect + go.opentelemetry.io/otel/sdk/export/metric v0.20.0 // indirect + go.opentelemetry.io/otel/sdk/metric v0.20.0 // indirect + go.opentelemetry.io/otel/trace v0.20.0 // indirect + go.opentelemetry.io/proto/otlp v0.7.0 // indirect + go.uber.org/atomic v1.7.0 // indirect + go.uber.org/multierr v1.6.0 // indirect + go.uber.org/zap v1.17.0 // indirect + golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 // indirect + golang.org/x/net v0.0.0-20211216030914-fe4d6282115f // indirect + golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect + golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 // indirect + golang.org/x/text v0.3.7 // indirect + golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba // indirect + google.golang.org/genproto v0.0.0-20210617175327-b9e0b3197ced // indirect + google.golang.org/protobuf v1.26.0 // indirect + gopkg.in/errgo.v1 v1.0.1 // indirect + gopkg.in/macaroon-bakery.v2 v2.0.1 // indirect + gopkg.in/macaroon.v2 v2.0.0 // indirect + gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect + gopkg.in/square/go-jose.v2 v2.3.1 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect + sigs.k8s.io/yaml v1.2.0 // indirect +) + +replace github.com/lightningnetwork/lnd v0.15.1-beta => github.com/breez/lnd v0.15.0-beta.rc6.0.20220831104847-00b86a81e57a From e16541c77dea319db0d2acd8f812fda947847073 Mon Sep 17 00:00:00 2001 From: Yaacov Akiba Slama Date: Mon, 19 Sep 2022 17:39:09 +0300 Subject: [PATCH 068/214] Use initial_chanid and confirmed_chanid to handle zerconf channels --- db.go | 29 +++++++++++------- go.mod | 92 +++++++++++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 106 insertions(+), 15 deletions(-) diff --git a/db.go b/db.go index acc3202..131a019 100644 --- a/db.go +++ b/db.go @@ -75,12 +75,22 @@ func registerPayment(destination, paymentHash, paymentSecret []byte, incomingAmo } func insertChannel(chanID uint64, channelPoint string, nodeID []byte, lastUpdate time.Time) error { - _, err := pgxPool.Exec(context.Background(), - `INSERT INTO - channels (chanid, channel_point, nodeid, last_update) + var query string + sid := lnwire.NewShortChanIDFromInt(chanID) + if sid.IsFake() { + query = `INSERT INTO + channels (initial_chanid, channel_point, nodeid, last_update) VALUES ($1, $2, $3, $4) - ON CONFLICT (chanid) DO UPDATE SET last_update=$4`, - chanID, channelPoint, nodeID, lastUpdate) + ON CONFLICT (channel_point) DO UPDATE SET last_update=$4` + } else { + query = `INSERT INTO + channels (initial_chanid, confirmed_chanid, channel_point, nodeid, last_update) + VALUES ($1, $1, $2, $3, $4) + ON CONFLICT (channel_point) DO UPDATE SET confirmed_chanid=$1, last_update=$4` + } + + _, err := pgxPool.Exec(context.Background(), + query, chanID, channelPoint, nodeID, lastUpdate) if err != nil { return fmt.Errorf("insertChannel(%v, %s, %x) error: %w", chanID, channelPoint, nodeID, err) @@ -94,9 +104,9 @@ func confirmedChannels(sNodeID string) (map[string]uint64, error) { return nil, fmt.Errorf("hex.DecodeString(%v) error: %w", sNodeID, err) } rows, err := pgxPool.Query(context.Background(), - `SELECT chanid, channel_point + `SELECT confirmed_chanid, channel_point FROM channels - WHERE nodeid=$1`, + WHERE nodeid=$1 AND confirmed_chanid IS NOT NULL`, nodeID) if err != nil { return nil, fmt.Errorf("channels(%x) error: %w", nodeID, err) @@ -112,10 +122,7 @@ func confirmedChannels(sNodeID string) (map[string]uint64, error) { if err != nil { return nil, fmt.Errorf("channels(%x) rows.Scan error: %w", nodeID, err) } - sid := lnwire.NewShortChanIDFromInt(chanID) - if !sid.IsFake() { - chans[channelPoint] = chanID - } + chans[channelPoint] = chanID } return chans, rows.Err() } diff --git a/go.mod b/go.mod index 7573a3a..aafcf5f 100644 --- a/go.mod +++ b/go.mod @@ -1,14 +1,11 @@ module github.com/breez/lspd -go 1.14 +go 1.19 require ( github.com/aws/aws-sdk-go v1.30.20 github.com/btcsuite/btcd v0.20.1-beta.0.20200730232343-1db1b6f8217f github.com/caddyserver/certmagic v0.11.2 - github.com/coreos/etcd v3.3.25+incompatible // indirect - github.com/coreos/go-semver v0.3.0 // indirect - github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f // indirect github.com/golang/protobuf v1.4.2 github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 github.com/jackc/pgtype v1.4.2 @@ -19,4 +16,91 @@ require ( google.golang.org/grpc v1.29.1 ) +require ( + github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect + github.com/aead/siphash v1.0.1 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f // indirect + github.com/btcsuite/btcutil v1.0.2 // indirect + github.com/btcsuite/btcutil/psbt v1.0.2 // indirect + github.com/btcsuite/btcwallet v0.11.1-0.20200814001439-1d31f4ea6fc5 // indirect + github.com/btcsuite/btcwallet/wallet/txauthor v1.0.0 // indirect + github.com/btcsuite/btcwallet/wallet/txrules v1.0.0 // indirect + github.com/btcsuite/btcwallet/wallet/txsizes v1.0.0 // indirect + github.com/btcsuite/btcwallet/walletdb v1.3.3 // indirect + github.com/btcsuite/btcwallet/wtxmgr v1.2.0 // indirect + github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd // indirect + github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 // indirect + github.com/cenkalti/backoff/v4 v4.0.0 // indirect + github.com/coreos/bbolt v1.3.3 // indirect + github.com/coreos/etcd v3.3.25+incompatible // indirect + github.com/coreos/go-semver v0.3.0 // indirect + github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f // indirect + github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/decred/dcrd/lru v1.0.0 // indirect + github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect + github.com/dustin/go-humanize v1.0.0 // indirect + github.com/go-acme/lego/v3 v3.7.0 // indirect + github.com/go-errors/errors v1.0.1 // indirect + github.com/gogo/protobuf v1.2.0 // indirect + github.com/google/btree v1.0.0 // indirect + github.com/google/uuid v1.1.1 // indirect + github.com/gorilla/websocket v1.4.2 // indirect + github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect + github.com/grpc-ecosystem/grpc-gateway v1.14.3 // indirect + github.com/jackc/chunkreader/v2 v2.0.1 // indirect + github.com/jackc/pgconn v1.6.4 // indirect + github.com/jackc/pgio v1.0.0 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgproto3/v2 v2.0.2 // indirect + github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect + github.com/jackc/puddle v1.1.1 // indirect + github.com/jmespath/go-jmespath v0.3.0 // indirect + github.com/jonboulle/clockwork v0.1.0 // indirect + github.com/jrick/logrotate v1.0.0 // indirect + github.com/json-iterator/go v1.1.9 // indirect + github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8 // indirect + github.com/kkdai/bstream v0.0.0-20181106074824-b3251f7901ec // indirect + github.com/klauspost/cpuid v1.2.3 // indirect + github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect + github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf // indirect + github.com/lightninglabs/neutrino v0.11.1-0.20200316235139-bffc52e8f200 // indirect + github.com/lightningnetwork/lnd/clock v1.0.1 // indirect + github.com/lightningnetwork/lnd/queue v1.0.4 // indirect + github.com/lightningnetwork/lnd/ticker v1.0.0 // indirect + github.com/ltcsuite/ltcd v0.0.0-20190101042124-f37f8bf35796 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect + github.com/miekg/dns v1.1.27 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.1 // indirect + github.com/prometheus/client_golang v1.1.0 // indirect + github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 // indirect + github.com/prometheus/common v0.6.0 // indirect + github.com/prometheus/procfs v0.0.3 // indirect + github.com/rogpeppe/fastuuid v1.2.0 // indirect + github.com/sirupsen/logrus v1.4.2 // indirect + github.com/soheilhy/cmux v0.1.4 // indirect + github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 // indirect + github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect + go.etcd.io/bbolt v1.3.5-0.20200615073812-232d8fc87f50 // indirect + go.uber.org/atomic v1.6.0 // indirect + go.uber.org/multierr v1.5.0 // indirect + go.uber.org/zap v1.14.1 // indirect + golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899 // indirect + golang.org/x/net v0.0.0-20200301022130-244492dfa37a // indirect + golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae // indirect + golang.org/x/text v0.3.3 // indirect + golang.org/x/time v0.0.0-20191024005414-555d28b269f0 // indirect + golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 // indirect + google.golang.org/genproto v0.0.0-20200305110556-506484158171 // indirect + google.golang.org/protobuf v1.23.0 // indirect + gopkg.in/errgo.v1 v1.0.1 // indirect + gopkg.in/macaroon-bakery.v2 v2.0.1 // indirect + gopkg.in/macaroon.v2 v2.0.0 // indirect + gopkg.in/square/go-jose.v2 v2.3.1 // indirect + gopkg.in/yaml.v2 v2.2.8 // indirect + sigs.k8s.io/yaml v1.1.0 // indirect +) + replace github.com/lightningnetwork/lnd v0.11.0-beta => github.com/breez/lnd v0.11.0-beta.rc4.0.20210125150416-0c10146b223c From 6594e3de273c32a603ba347b2945d003e4435e49 Mon Sep 17 00:00:00 2001 From: Yaacov Akiba Slama Date: Mon, 19 Sep 2022 20:47:21 +0300 Subject: [PATCH 069/214] Convert between int64 (in postgresql) and uint64 (short channel id) --- db.go | 6 +++--- forwarding_history.go | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/db.go b/db.go index 131a019..b4b8ea6 100644 --- a/db.go +++ b/db.go @@ -90,7 +90,7 @@ func insertChannel(chanID uint64, channelPoint string, nodeID []byte, lastUpdate } _, err := pgxPool.Exec(context.Background(), - query, chanID, channelPoint, nodeID, lastUpdate) + query, int64(chanID), channelPoint, nodeID, lastUpdate) if err != nil { return fmt.Errorf("insertChannel(%v, %s, %x) error: %w", chanID, channelPoint, nodeID, err) @@ -115,14 +115,14 @@ func confirmedChannels(sNodeID string) (map[string]uint64, error) { chans := make(map[string]uint64) for rows.Next() { var ( - chanID uint64 + chanID int64 channelPoint string ) err = rows.Scan(&chanID, &channelPoint) if err != nil { return nil, fmt.Errorf("channels(%x) rows.Scan error: %w", nodeID, err) } - chans[channelPoint] = chanID + chans[channelPoint] = uint64(chanID) } return chans, rows.Err() } diff --git a/forwarding_history.go b/forwarding_history.go index 4a98caa..9ad9066 100644 --- a/forwarding_history.go +++ b/forwarding_history.go @@ -28,7 +28,7 @@ func (cfe *copyFromEvents) Values() ([]interface{}, error) { event := cfe.events[cfe.idx] values := []interface{}{ event.TimestampNs, - event.ChanIdIn, event.ChanIdOut, + int64(event.ChanIdIn), int64(event.ChanIdOut), event.AmtInMsat, event.AmtOutMsat} return values, nil } From 3a4892ce8a9d8c4837f32d6020763debdeff9f19 Mon Sep 17 00:00:00 2001 From: Yaacov Akiba Slama Date: Mon, 19 Sep 2022 22:32:19 +0300 Subject: [PATCH 070/214] Add data migrations --- .../000008_one_record_per_channel.down.sql | 21 ++++++++++++++++++ .../000008_one_record_per_channel.up.sql | 22 +++++++++++++++++++ 2 files changed, 43 insertions(+) create mode 100644 postgresql/migrations/000008_one_record_per_channel.down.sql create mode 100644 postgresql/migrations/000008_one_record_per_channel.up.sql diff --git a/postgresql/migrations/000008_one_record_per_channel.down.sql b/postgresql/migrations/000008_one_record_per_channel.down.sql new file mode 100644 index 0000000..4ba53f6 --- /dev/null +++ b/postgresql/migrations/000008_one_record_per_channel.down.sql @@ -0,0 +1,21 @@ +ALTER INDEX public.channels_nodeid_idx RENAME TO channels_new_nodeid_idx; +ALTER INDEX public.channels_channel_point_pkey RENAME TO channels_new_channel_point_pkey +ALTER TABLE public.channels RENAME TO channels_new; + +CREATE TABLE public.channels ( + chanid int8 NOT NULL, + channel_point varchar NULL, + nodeid bytea NULL, + last_update timestamp NULL, + CONSTRAINT chanid_pkey PRIMARY KEY (chanid) +); +CREATE INDEX channels_nodeid_idx ON public.channels USING btree (nodeid); + +INSERT INTO public.channels +SELECT initial_chanid chanid, channel_point, nodeid, last_update FROM channels_new; + +INSERT INTO public.channels +SELECT confirmed_chanid chanid, channel_point, nodeid, last_update FROM channels_new + WHERE confirmed_chanid IS NOT NULL AND confirmed_chanid <> initial_chanid; + +DROP TABLE channels_new; diff --git a/postgresql/migrations/000008_one_record_per_channel.up.sql b/postgresql/migrations/000008_one_record_per_channel.up.sql new file mode 100644 index 0000000..2261a23 --- /dev/null +++ b/postgresql/migrations/000008_one_record_per_channel.up.sql @@ -0,0 +1,22 @@ +ALTER INDEX public.channels_nodeid_idx RENAME TO channels_old_nodeid_idx; +ALTER INDEX public.chanid_pkey RENAME TO channels_old_chanid_pkey; +ALTER TABLE public.channels RENAME TO channels_old; + +CREATE TABLE public.channels ( + initial_chanid int8 NOT NULL, + confirmed_chanid int8 NULL, + channel_point varchar NOT NULL, + nodeid bytea NOT NULL, + last_update timestamp NULL, + CONSTRAINT channels_channel_point_pkey PRIMARY KEY (channel_point) +); +CREATE INDEX channels_nodeid_idx ON public.channels USING btree (nodeid); + +INSERT INTO public.channels +SELECT + min(chanid) initial_chanid, + CASE WHEN (max(chanid) >> 40) < (3 << 17) THEN NULL ELSE max(chanid) END confirmed_chanid, + channel_point, nodeid, max(last_update) last_update +FROM channels_old GROUP BY channel_point, nodeid; + +DROP TABLE public.channels_old; From 7ceb5bf98866c249c4960e0d9d8e32e159032d7b Mon Sep 17 00:00:00 2001 From: Yaacov Akiba Slama Date: Wed, 21 Sep 2022 12:18:37 +0300 Subject: [PATCH 071/214] Add some logging in insertChannel --- db.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/db.go b/db.go index b4b8ea6..81ee7b4 100644 --- a/db.go +++ b/db.go @@ -89,12 +89,16 @@ func insertChannel(chanID uint64, channelPoint string, nodeID []byte, lastUpdate ON CONFLICT (channel_point) DO UPDATE SET confirmed_chanid=$1, last_update=$4` } - _, err := pgxPool.Exec(context.Background(), + c, err := pgxPool.Exec(context.Background(), query, int64(chanID), channelPoint, nodeID, lastUpdate) if err != nil { + log.Printf("insertChannel(%v, %s, %x) error: %v", + chanID, channelPoint, nodeID, err) return fmt.Errorf("insertChannel(%v, %s, %x) error: %w", chanID, channelPoint, nodeID, err) } + log.Printf("insertChannel(%v, %s, %x) result: %v", + chanID, channelPoint, nodeID, c.String()) return nil } From 4e7c6d9afffc123630a1f3b5855f5589b9c65d8d Mon Sep 17 00:00:00 2001 From: Yaacov Akiba Slama Date: Mon, 3 Oct 2022 11:08:57 +0300 Subject: [PATCH 072/214] Use Anchors commitment transaction when opening a channel --- intercept.go | 1 + 1 file changed, 1 insertion(+) diff --git a/intercept.go b/intercept.go index 59d28e8..1c20b48 100644 --- a/intercept.go +++ b/intercept.go @@ -64,6 +64,7 @@ func openChannel(ctx context.Context, client lnrpc.LightningClient, paymentHash, LocalFundingAmount: capacity, TargetConf: 6, Private: true, + CommitmentType: lnrpc.CommitmentType_ANCHORS, ZeroConf: true, }) if err != nil { From 80032bed088ece9ae101b112a3c43b12d40ccc4a Mon Sep 17 00:00:00 2001 From: Yaacov Akiba Slama Date: Mon, 3 Oct 2022 11:09:49 +0300 Subject: [PATCH 073/214] Fix logging in insertChannel --- db.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/db.go b/db.go index 9789528..b9337bc 100644 --- a/db.go +++ b/db.go @@ -85,10 +85,10 @@ func insertChannel(initialChanID, confirmedChanId uint64, channelPoint string, n if err != nil { log.Printf("insertChannel(%v, %v, %s, %x) error: %v", initialChanID, confirmedChanId, channelPoint, nodeID, err) - return fmt.Errorf("insertChannel(%v, %s, %x) error: %w", - initialChanID, confirmedChanId, nodeID, err) + return fmt.Errorf("insertChannel(%v, %v, %s, %x) error: %w", + initialChanID, confirmedChanId, channelPoint, nodeID, err) } - log.Printf("insertChannel(%v, %s, %x) result: %v", + log.Printf("insertChannel(%v, %v, %x) result: %v", initialChanID, confirmedChanId, nodeID, c.String()) return nil } From 02ceb92286e68a7fe782d93f5c6e4b189e335da9 Mon Sep 17 00:00:00 2001 From: Yaacov Akiba Slama Date: Mon, 3 Oct 2022 11:10:38 +0300 Subject: [PATCH 074/214] Fix insertChannel in the ON CONFLICT arm --- db.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db.go b/db.go index b9337bc..02d77b4 100644 --- a/db.go +++ b/db.go @@ -78,7 +78,7 @@ func insertChannel(initialChanID, confirmedChanId uint64, channelPoint string, n query := `INSERT INTO channels (initial_chanid, confirmed_chanid, channel_point, nodeid, last_update) VALUES ($1, NULLIF($2, 0), $3, $4, $5) - ON CONFLICT (channel_point) DO UPDATE SET confirmed_chanid=NULLIF($2,0), last_update=$4` + ON CONFLICT (channel_point) DO UPDATE SET confirmed_chanid=NULLIF($2,0), last_update=$5` c, err := pgxPool.Exec(context.Background(), query, int64(initialChanID), int64(confirmedChanId), channelPoint, nodeID, lastUpdate) From 6f292003f94319d5314c834a6cc0520460dd1b66 Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Thu, 17 Nov 2022 14:19:07 +0100 Subject: [PATCH 075/214] add LightningClient interface --- lightning_client.go | 31 ++++++++++++++++++++++++++++++ outpoint.go | 19 ++++++++++++++++++ short_channel_id.go | 47 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 97 insertions(+) create mode 100644 lightning_client.go create mode 100644 outpoint.go create mode 100644 short_channel_id.go diff --git a/lightning_client.go b/lightning_client.go new file mode 100644 index 0000000..f26116d --- /dev/null +++ b/lightning_client.go @@ -0,0 +1,31 @@ +package main + +import "github.com/btcsuite/btcd/wire" + +type GetInfoResult struct { + Alias string + Pubkey string +} + +type GetChannelResult struct { + InitialChannelID ShortChannelID + ConfirmedChannelID ShortChannelID +} + +type OpenChannelRequest struct { + Destination []byte + CapacitySat uint64 + MinHtlcMsat uint64 + TargetConf uint32 + IsPrivate bool + IsZeroConf bool +} + +type LightningClient interface { + GetInfo() (*GetInfoResult, error) + IsConnected(destination []byte) (*bool, error) + OpenChannel(req *OpenChannelRequest) (*wire.OutPoint, error) + GetChannel(peerID []byte, channelPoint wire.OutPoint) (*GetChannelResult, error) + GetNodeChannelCount(nodeID []byte) (int, error) + GetClosedChannels(nodeID string, channelPoints map[string]uint64) (map[string]uint64, error) +} diff --git a/outpoint.go b/outpoint.go new file mode 100644 index 0000000..a25bf8b --- /dev/null +++ b/outpoint.go @@ -0,0 +1,19 @@ +package main + +import ( + "log" + + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" +) + +func NewOutPoint(fundingTxID []byte, index uint32) (*wire.OutPoint, error) { + var h chainhash.Hash + err := h.SetBytes(fundingTxID) + if err != nil { + log.Printf("h.SetBytes(%x) error: %v", fundingTxID, err) + return nil, err + } + + return wire.NewOutPoint(&h, index), nil +} diff --git a/short_channel_id.go b/short_channel_id.go new file mode 100644 index 0000000..c8207a2 --- /dev/null +++ b/short_channel_id.go @@ -0,0 +1,47 @@ +package main + +import ( + "fmt" + "strconv" + "strings" +) + +type ShortChannelID uint64 + +func NewShortChannelIDFromString(channelID string) (*ShortChannelID, error) { + parts := strings.Split(channelID, "x") + if len(parts) != 3 { + return nil, fmt.Errorf("expected 3 parts, got %d", len(parts)) + } + + blockHeight, err := strconv.Atoi(parts[0]) + if err != nil { + return nil, err + } + + txIndex, err := strconv.Atoi(parts[1]) + if err != nil { + return nil, err + } + + outputIndex, err := strconv.Atoi(parts[2]) + if err != nil { + return nil, err + } + + result := ShortChannelID( + (uint64(outputIndex) & 0xFFFF) + + ((uint64(txIndex) << 16) & 0xFFFFFF0000) + + ((uint64(blockHeight) << 40) & 0xFFFFFF0000000000), + ) + + return &result, nil +} + +func (c *ShortChannelID) ToString() string { + u := uint64(*c) + blockHeight := (u >> 40) & 0xFFFFFF + txIndex := (u >> 16) & 0xFFFFFF + outputIndex := u & 0xFFFF + return fmt.Sprintf("%dx%dx%d", blockHeight, txIndex, outputIndex) +} From b254f4d9856e73459a55baa8782b23b84ef9a306 Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Thu, 17 Nov 2022 14:21:21 +0100 Subject: [PATCH 076/214] add LndClient implementation --- lnd_client.go | 215 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 215 insertions(+) create mode 100644 lnd_client.go diff --git a/lnd_client.go b/lnd_client.go new file mode 100644 index 0000000..bf169cb --- /dev/null +++ b/lnd_client.go @@ -0,0 +1,215 @@ +package main + +import ( + "context" + "crypto/x509" + "encoding/hex" + "fmt" + "log" + "os" + "strings" + + "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" + "google.golang.org/grpc/metadata" +) + +type LndClient struct { + client lnrpc.LightningClient + routerClient routerrpc.RouterClient + chainNotifierClient chainrpc.ChainNotifierClient + conn *grpc.ClientConn +} + +func NewLndClient() *LndClient { + // 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) + } + + client := lnrpc.NewLightningClient(conn) + routerClient := routerrpc.NewRouterClient(conn) + chainNotifierClient := chainrpc.NewChainNotifierClient(conn) + return &LndClient{ + client: client, + routerClient: routerClient, + chainNotifierClient: chainNotifierClient, + conn: conn, + } +} + +func (c *LndClient) Close() { + c.conn.Close() +} + +func (c *LndClient) GetInfo() (*GetInfoResult, error) { + info, err := c.client.GetInfo(macaroonContext(), &lnrpc.GetInfoRequest{}) + if err != nil { + log.Printf("LND: client.GetInfo() error: %v", err) + return nil, err + } + + return &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(macaroonContext(), &lnrpc.ListPeersRequest{LatestError: true}) + if err != nil { + log.Printf("LND: client.ListPeers() error: %v", err) + return nil, fmt.Errorf("LND: client.ListPeers() error: %w", err) + } + for _, peer := range r.Peers { + if pubKey == peer.PubKey { + log.Printf("destination online: %x", destination) + result := true + return &result, nil + } + } + + log.Printf("LND: destination offline: %x", destination) + result := false + return &result, nil +} + +func (c *LndClient) OpenChannel(req *OpenChannelRequest) (*wire.OutPoint, error) { + channelPoint, err := c.client.OpenChannelSync(macaroonContext(), &lnrpc.OpenChannelRequest{ + NodePubkey: req.Destination, + LocalFundingAmount: int64(req.CapacitySat), + TargetConf: int32(req.TargetConf), + PushSat: 0, + Private: req.IsPrivate, + CommitmentType: lnrpc.CommitmentType_ANCHORS, + ZeroConf: req.IsZeroConf, + }) + + 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 := 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) (*GetChannelResult, error) { + r, err := c.client.ListChannels(macaroonContext(), &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 &GetChannelResult{ + InitialChannelID: ShortChannelID(c.ChanId), + ConfirmedChannelID: 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) + clientCtx := metadata.AppendToOutgoingContext(context.Background(), "macaroon", os.Getenv("LND_MACAROON_HEX")) + listResponse, err := c.client.ListChannels(clientCtx, &lnrpc.ListChannelsRequest{}) + if err != nil { + return 0, err + } + + pendingResponse, err := c.client.PendingChannels(clientCtx, &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(macaroonContext(), &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 macaroonContext() context.Context { + return metadata.AppendToOutgoingContext(context.Background(), "macaroon", os.Getenv("LND_MACAROON_HEX")) +} From 465faf8f2c1f3797e57a213c2ac34a27e4bc9930 Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Thu, 17 Nov 2022 14:44:39 +0100 Subject: [PATCH 077/214] use LndClient --- forwarding_history.go | 18 +++--- intercept.go | 10 +-- server.go | 147 +++++++----------------------------------- 3 files changed, 39 insertions(+), 136 deletions(-) diff --git a/forwarding_history.go b/forwarding_history.go index 7fd4f18..997c6b5 100644 --- a/forwarding_history.go +++ b/forwarding_history.go @@ -38,12 +38,12 @@ func (cfe *copyFromEvents) Err() error { return cfe.err } -func channelsSynchronize(client chainrpc.ChainNotifierClient) { +func channelsSynchronize(client *LndClient) { lastSync := time.Now().Add(-6 * time.Minute) for { cancellableCtx, cancel := context.WithCancel(context.Background()) clientCtx := metadata.AppendToOutgoingContext(cancellableCtx, "macaroon", os.Getenv("LND_MACAROON_HEX")) - stream, err := client.RegisterBlockEpochNtfn(clientCtx, &chainrpc.BlockEpoch{}) + stream, err := client.chainNotifierClient.RegisterBlockEpochNtfn(clientCtx, &chainrpc.BlockEpoch{}) if err != nil { log.Printf("chainNotifierClient.RegisterBlockEpochNtfn(): %v", err) cancel() @@ -59,7 +59,7 @@ func channelsSynchronize(client chainrpc.ChainNotifierClient) { } if lastSync.Add(5 * time.Minute).Before(time.Now()) { time.Sleep(30 * time.Second) - err = channelsSynchronizeOnce() + err = channelsSynchronizeOnce(client) lastSync = time.Now() log.Printf("channelsSynchronizeOnce() err: %v", err) } @@ -68,10 +68,10 @@ func channelsSynchronize(client chainrpc.ChainNotifierClient) { } } -func channelsSynchronizeOnce() error { +func channelsSynchronizeOnce(client *LndClient) error { log.Printf("channelsSynchronizeOnce - begin") clientCtx := metadata.AppendToOutgoingContext(context.Background(), "macaroon", os.Getenv("LND_MACAROON_HEX")) - channels, err := client.ListChannels(clientCtx, &lnrpc.ListChannelsRequest{PrivateOnly: true}) + channels, err := client.client.ListChannels(clientCtx, &lnrpc.ListChannelsRequest{PrivateOnly: true}) if err != nil { log.Printf("ListChannels error: %v", err) return fmt.Errorf("client.ListChannels() error: %w", err) @@ -102,15 +102,15 @@ func channelsSynchronizeOnce() error { return nil } -func forwardingHistorySynchronize() { +func forwardingHistorySynchronize(client *LndClient) { for { - err := forwardingHistorySynchronizeOnce() + err := forwardingHistorySynchronizeOnce(client) log.Printf("forwardingHistorySynchronizeOnce() err: %v", err) time.Sleep(1 * time.Minute) } } -func forwardingHistorySynchronizeOnce() error { +func forwardingHistorySynchronizeOnce(client *LndClient) error { last, err := lastForwardingEvent() if err != nil { return fmt.Errorf("lastForwardingEvent() error: %w", err) @@ -126,7 +126,7 @@ func forwardingHistorySynchronizeOnce() error { clientCtx := metadata.AppendToOutgoingContext(context.Background(), "macaroon", os.Getenv("LND_MACAROON_HEX")) indexOffset := uint32(0) for { - forwardHistory, err := client.ForwardingHistory(clientCtx, &lnrpc.ForwardingHistoryRequest{ + forwardHistory, err := client.client.ForwardingHistory(clientCtx, &lnrpc.ForwardingHistoryRequest{ StartTime: uint64(last), EndTime: endTime, NumMaxEvents: 10000, diff --git a/intercept.go b/intercept.go index 1c20b48..30b3640 100644 --- a/intercept.go +++ b/intercept.go @@ -113,11 +113,11 @@ func failForwardSend(interceptorClient routerrpc.Router_HtlcInterceptorClient, i }) } -func intercept() { +func intercept(client *LndClient) { for { cancellableCtx, cancel := context.WithCancel(context.Background()) clientCtx := metadata.AppendToOutgoingContext(cancellableCtx, "macaroon", os.Getenv("LND_MACAROON_HEX")) - interceptorClient, err := routerClient.HtlcInterceptor(clientCtx) + interceptorClient, err := client.routerClient.HtlcInterceptor(clientCtx) if err != nil { log.Printf("routerClient.HtlcInterceptor(): %v", err) cancel() @@ -162,7 +162,7 @@ func intercept() { if fundingTxID == nil { if bytes.Compare(paymentHash, request.PaymentHash) == 0 { - fundingTxID, fundingTxOutnum, err = openChannel(clientCtx, client, request.PaymentHash, destination, incomingAmountMsat) + fundingTxID, fundingTxOutnum, err = openChannel(clientCtx, client.client, request.PaymentHash, destination, incomingAmountMsat) log.Printf("openChannel(%x, %v) err: %v", destination, incomingAmountMsat, err) if err != nil { failForwardSend(interceptorClient, request.IncomingCircuitKey) @@ -170,7 +170,7 @@ func intercept() { } } else { //probing failureCode := lnrpc.Failure_TEMPORARY_CHANNEL_FAILURE - if err := isConnected(clientCtx, client, destination); err == nil { + if err := isConnected(clientCtx, client.client, destination); err == nil { failureCode = lnrpc.Failure_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS } interceptorClient.Send(&routerrpc.ForwardHtlcInterceptResponse{ @@ -281,7 +281,7 @@ func resumeOrCancel( ) { deadline := time.Now().Add(10 * time.Second) for { - initialChanID, confirmedChanID := getChannel(ctx, client, destination, channelPoint) + initialChanID, confirmedChanID := getChannel(ctx, client.client, destination, channelPoint) if initialChanID != 0 { interceptorClient.Send(&routerrpc.ForwardHtlcInterceptResponse{ IncomingCircuitKey: incomingCircuitKey, diff --git a/server.go b/server.go index 88dca73..ff84cc6 100644 --- a/server.go +++ b/server.go @@ -3,14 +3,12 @@ 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" @@ -18,16 +16,13 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "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" ) @@ -48,9 +43,7 @@ const ( type server struct{} var ( - client lnrpc.LightningClient - routerClient routerrpc.RouterClient - chainNotifierClient chainrpc.ChainNotifierClient + client *LndClient openChannelReqGroup singleflight.Group privateKey *btcec.PrivateKey publicKey *btcec.PublicKey @@ -105,12 +98,12 @@ func (s *server) RegisterPayment(ctx context.Context, in *lspdrpc.RegisterPaymen 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) + pubkey, err := hex.DecodeString(in.Pubkey) if err != nil { return nil, err } - pendingChannels, err := getPendingNodeChannels(in.Pubkey) + + channelCount, err := client.GetNodeChannelCount(pubkey) if err != nil { return nil, err } @@ -126,33 +119,25 @@ func (s *server) OpenChannel(ctx context.Context, in *lspdrpc.OpenChannelRequest } 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, + var outPoint *wire.OutPoint + if channelCount == 0 { + outPoint, err = client.OpenChannel(&OpenChannelRequest{ + CapacitySat: uint64(channelAmount), + Destination: pubkey, + TargetConf: targetConf, + MinHtlcMsat: minHtlcMsat, + IsPrivate: 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() - } + log.Printf("Response from OpenChannel: (TX: %v)", outPoint.String()) } - return &lspdrpc.OpenChannelReply{TxHash: txidStr, OutputIndex: outputIndex}, nil + + return &lspdrpc.OpenChannelReply{TxHash: outPoint.Hash.String(), OutputIndex: outPoint.Index}, nil }) if err != nil { @@ -212,10 +197,10 @@ func (s *server) CheckChannels(ctx context.Context, in *lspdrpc.Encrypted) (*lsp 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) + closedChannels, err := client.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) + log.Printf("GetClosedChannels(%v) error: %v", checkChannelsRequest.FakeChannels, err) + return nil, fmt.Errorf("GetClosedChannels(%v) error: %w", checkChannelsRequest.FakeChannels, err) } checkChannelsReply := lspdrpc.CheckChannelsReply{ NotFakeChannels: notFakeChannels, @@ -256,72 +241,6 @@ func getNotFakeChannels(nodeID string, channelPoints map[string]uint64) (map[str 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() @@ -363,25 +282,9 @@ func main() { } } - // 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, "") + client = NewLndClient() - // 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{}) + info, err := client.GetInfo() if err != nil { log.Fatalf("client.GetInfo() error: %v", err) } @@ -389,13 +292,13 @@ func main() { nodeName = info.Alias } if nodePubkey == "" { - nodePubkey = info.IdentityPubkey + nodePubkey = info.Pubkey } - go intercept() + go intercept(client) - go forwardingHistorySynchronize() - go channelsSynchronize(chainNotifierClient) + go forwardingHistorySynchronize(client) + go channelsSynchronize(client) s := grpc.NewServer( grpc_middleware.WithUnaryServerChain(func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { From 126754281e5bf2336f5589c014efa5c82b9450eb Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Thu, 17 Nov 2022 14:53:53 +0100 Subject: [PATCH 078/214] Start and stop methods for the server --- server.go | 99 +++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 64 insertions(+), 35 deletions(-) diff --git a/server.go b/server.go index ff84cc6..2577e04 100644 --- a/server.go +++ b/server.go @@ -13,18 +13,18 @@ import ( "github.com/breez/lspd/btceclegacy" lspdrpc "github.com/breez/lspd/rpc" "github.com/golang/protobuf/proto" + grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" "github.com/caddyserver/certmagic" - grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware" "github.com/lightningnetwork/lnd/lnwire" "golang.org/x/sync/singleflight" - "google.golang.org/grpc" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/metadata" - "google.golang.org/grpc/status" ) const ( @@ -40,7 +40,10 @@ const ( maxInactiveDuration = 45 * 24 * 3600 ) -type server struct{} +type server struct { + lis net.Listener + s *grpc.Server +} var ( client *LndClient @@ -241,21 +244,11 @@ func getNotFakeChannels(nodeID string, channelPoints map[string]uint64) (map[str return r, 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) - } +func NewGrpcServer() *server { + return &server{} +} +func (s *server) Start() error { 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) @@ -282,6 +275,51 @@ func main() { } } + srv := 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(srv, &server{}) + + s.s = srv + s.lis = lis + if err := srv.Serve(lis); err != nil { + return fmt.Errorf("failed to serve: %v", err) + } + + return nil +} + +func (s *server) Stop() { + srv := s.s + if srv != nil { + srv.GracefulStop() + } +} + +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) + } + client = NewLndClient() info, err := client.GetInfo() @@ -300,20 +338,11 @@ func main() { go forwardingHistorySynchronize(client) go channelsSynchronize(client) - 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) + s := NewGrpcServer() + err = s.Start() + if err != nil { + log.Fatalf("%v", err) } + + log.Printf("lspd exited") } From 8b5f5f80b7c3446d3107364729486b55d7ac2921 Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Thu, 17 Nov 2022 15:23:15 +0100 Subject: [PATCH 079/214] Make openChannel use LightningClient --- db.go | 21 +++++++++++++++------ email.go | 14 +------------- intercept.go | 42 ++++++++++++++++-------------------------- 3 files changed, 32 insertions(+), 45 deletions(-) diff --git a/db.go b/db.go index 02d77b4..5216a48 100644 --- a/db.go +++ b/db.go @@ -8,6 +8,7 @@ import ( "os" "time" + "github.com/btcsuite/btcd/wire" "github.com/jackc/pgtype" "github.com/jackc/pgx/v4" "github.com/jackc/pgx/v4/pgxpool" @@ -26,7 +27,7 @@ func pgConnect() error { return nil } -func paymentInfo(htlcPaymentHash []byte) ([]byte, []byte, []byte, int64, int64, []byte, uint32, error) { +func paymentInfo(htlcPaymentHash []byte) ([]byte, []byte, []byte, int64, int64, *wire.OutPoint, error) { var ( paymentHash, paymentSecret, destination []byte incomingAmountMsat, outgoingAmountMsat int64 @@ -42,18 +43,26 @@ func paymentInfo(htlcPaymentHash []byte) ([]byte, []byte, []byte, int64, int64, if err == pgx.ErrNoRows { err = nil } - return nil, nil, nil, 0, 0, nil, 0, err + return nil, nil, nil, 0, 0, nil, err } - return paymentHash, paymentSecret, destination, incomingAmountMsat, outgoingAmountMsat, fundingTxID, uint32(fundingTxOutnum.Int), nil + + var cp *wire.OutPoint + if fundingTxID != nil { + cp, err = NewOutPoint(fundingTxID, uint32(fundingTxOutnum.Int)) + if err != nil { + log.Printf("invalid funding txid in database %x", fundingTxID) + } + } + return paymentHash, paymentSecret, destination, incomingAmountMsat, outgoingAmountMsat, cp, nil } -func setFundingTx(paymentHash, fundingTxID []byte, fundingTxOutnum int) error { +func setFundingTx(paymentHash []byte, channelPoint *wire.OutPoint) error { commandTag, err := pgxPool.Exec(context.Background(), `UPDATE payments SET funding_tx_id = $2, funding_tx_outnum = $3 WHERE payment_hash=$1`, - paymentHash, fundingTxID, fundingTxOutnum) - log.Printf("setFundingTx(%x, %x, %v): %s err: %v", paymentHash, fundingTxID, fundingTxOutnum, commandTag, err) + paymentHash, channelPoint.Hash[:], channelPoint.Index) + log.Printf("setFundingTx(%x, %x, %v): %s err: %v", paymentHash, channelPoint.Hash[:], channelPoint.Index, commandTag, err) return err } diff --git a/email.go b/email.go index 0cccb39..b0a5fdb 100644 --- a/email.go +++ b/email.go @@ -4,7 +4,6 @@ import ( "bytes" "encoding/hex" "encoding/json" - "fmt" "html/template" "log" "os" @@ -14,8 +13,6 @@ import ( "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/ses" - "github.com/btcsuite/btcd/chaincfg/chainhash" - "github.com/btcsuite/btcd/wire" ) const ( @@ -127,16 +124,7 @@ func sendChannelMismatchNotification(nodeID string, notFakeChannels, closedChann func sendOpenChannelEmailNotification( paymentHash []byte, incomingAmountMsat int64, destination []byte, capacity int64, - fundingTxID []byte, fundingTxOutnum uint32) error { - - var h chainhash.Hash - err := h.SetBytes(fundingTxID) - if err != nil { - log.Printf("h.SetBytes(%x) error: %v", fundingTxID, err) - return fmt.Errorf("h.SetBytes(%x) error: %w", fundingTxID, err) - } - channelPoint := wire.NewOutPoint(&h, fundingTxOutnum).String() - + channelPoint string) error { var html bytes.Buffer tpl := ` diff --git a/intercept.go b/intercept.go index 30b3640..a5c3605 100644 --- a/intercept.go +++ b/intercept.go @@ -11,7 +11,6 @@ import ( "time" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/htlcswitch/hop" "github.com/lightningnetwork/lnd/lnrpc" @@ -54,33 +53,31 @@ func isConnected(ctx context.Context, client lnrpc.LightningClient, destination return fmt.Errorf("destination offline") } -func openChannel(ctx context.Context, client lnrpc.LightningClient, paymentHash, destination []byte, incomingAmountMsat int64) ([]byte, uint32, error) { +func openChannel(client LightningClient, paymentHash, destination []byte, incomingAmountMsat int64) (*wire.OutPoint, error) { capacity := incomingAmountMsat/1000 + additionalChannelCapacity if capacity == publicChannelAmount { capacity++ } - channelPoint, err := client.OpenChannelSync(ctx, &lnrpc.OpenChannelRequest{ - NodePubkey: destination, - LocalFundingAmount: capacity, - TargetConf: 6, - Private: true, - CommitmentType: lnrpc.CommitmentType_ANCHORS, - ZeroConf: true, + channelPoint, err := client.OpenChannel(&OpenChannelRequest{ + Destination: destination, + CapacitySat: uint64(capacity), + TargetConf: 6, + IsPrivate: true, + IsZeroConf: true, }) if err != nil { log.Printf("client.OpenChannelSync(%x, %v) error: %v", destination, capacity, err) - return nil, 0, err + return nil, err } sendOpenChannelEmailNotification( paymentHash, incomingAmountMsat, destination, capacity, - channelPoint.GetFundingTxidBytes(), - channelPoint.OutputIndex, + channelPoint.String(), ) - err = setFundingTx(paymentHash, channelPoint.GetFundingTxidBytes(), int(channelPoint.OutputIndex)) - return channelPoint.GetFundingTxidBytes(), channelPoint.OutputIndex, err + err = setFundingTx(paymentHash, channelPoint) + return channelPoint, err } func getChannel(ctx context.Context, client lnrpc.LightningClient, node []byte, channelPoint string) (uint64, uint64) { @@ -150,7 +147,7 @@ func intercept(client *LndClient) { request.OnionBlob, ) - paymentHash, paymentSecret, destination, incomingAmountMsat, outgoingAmountMsat, fundingTxID, fundingTxOutnum, err := paymentInfo(request.PaymentHash) + paymentHash, paymentSecret, destination, incomingAmountMsat, outgoingAmountMsat, channelPoint, err := paymentInfo(request.PaymentHash) if err != nil { log.Printf("paymentInfo(%x) error: %v", request.PaymentHash, err) failForwardSend(interceptorClient, request.IncomingCircuitKey) @@ -160,9 +157,9 @@ func intercept(client *LndClient) { paymentHash, paymentSecret, destination, incomingAmountMsat, outgoingAmountMsat) if paymentSecret != nil { - if fundingTxID == nil { + if channelPoint == nil { if bytes.Compare(paymentHash, request.PaymentHash) == 0 { - fundingTxID, fundingTxOutnum, err = openChannel(clientCtx, client.client, request.PaymentHash, destination, incomingAmountMsat) + channelPoint, err = openChannel(client, request.PaymentHash, destination, incomingAmountMsat) log.Printf("openChannel(%x, %v) err: %v", destination, incomingAmountMsat, err) if err != nil { failForwardSend(interceptorClient, request.IncomingCircuitKey) @@ -244,17 +241,10 @@ func intercept(client *LndClient) { failForwardSend(interceptorClient, request.IncomingCircuitKey) continue } - var h chainhash.Hash - err = h.SetBytes(fundingTxID) - if err != nil { - log.Printf("h.SetBytes(%x) error: %v", fundingTxID, err) - failForwardSend(interceptorClient, request.IncomingCircuitKey) - continue - } - channelPoint := wire.NewOutPoint(&h, fundingTxOutnum).String() + go resumeOrCancel( clientCtx, interceptorClient, request.IncomingCircuitKey, destination, - channelPoint, uint64(amt), onionBlob.Bytes(), + channelPoint.String(), uint64(amt), onionBlob.Bytes(), ) } else { From ecfb87886043c3d96f20119c28207cfe47145cb9 Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Thu, 17 Nov 2022 15:28:56 +0100 Subject: [PATCH 080/214] Move isConnected to LightningClient --- intercept.go | 22 +++------------------- 1 file changed, 3 insertions(+), 19 deletions(-) diff --git a/intercept.go b/intercept.go index a5c3605..70fde05 100644 --- a/intercept.go +++ b/intercept.go @@ -3,7 +3,6 @@ package main import ( "bytes" "context" - "encoding/hex" "fmt" "log" "math/big" @@ -36,23 +35,6 @@ func checkPayment(incomingAmountMsat, outgoingAmountMsat int64) error { return nil } -func isConnected(ctx context.Context, client lnrpc.LightningClient, destination []byte) error { - pubKey := hex.EncodeToString(destination) - r, err := client.ListPeers(ctx, &lnrpc.ListPeersRequest{LatestError: true}) - if err != nil { - log.Printf("client.ListPeers() error: %v", err) - return fmt.Errorf("client.ListPeers() error: %w", err) - } - for _, peer := range r.Peers { - if pubKey == peer.PubKey { - log.Printf("destination online: %x", destination) - return nil - } - } - log.Printf("destination offline: %x", destination) - return fmt.Errorf("destination offline") -} - func openChannel(client LightningClient, paymentHash, destination []byte, incomingAmountMsat int64) (*wire.OutPoint, error) { capacity := incomingAmountMsat/1000 + additionalChannelCapacity if capacity == publicChannelAmount { @@ -167,9 +149,11 @@ func intercept(client *LndClient) { } } else { //probing failureCode := lnrpc.Failure_TEMPORARY_CHANNEL_FAILURE - if err := isConnected(clientCtx, client.client, destination); err == nil { + isConnected, _ := client.IsConnected(destination) + if err != nil || !*isConnected { failureCode = lnrpc.Failure_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS } + interceptorClient.Send(&routerrpc.ForwardHtlcInterceptResponse{ IncomingCircuitKey: request.IncomingCircuitKey, Action: routerrpc.ResolveHoldForwardAction_FAIL, From d6b30f139721bff731b22fea5d193da15edb0ff6 Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Thu, 17 Nov 2022 15:35:43 +0100 Subject: [PATCH 081/214] Move getChannel to LightningClient --- intercept.go | 41 +++++++++++------------------------------ 1 file changed, 11 insertions(+), 30 deletions(-) diff --git a/intercept.go b/intercept.go index 70fde05..f9a2b95 100644 --- a/intercept.go +++ b/intercept.go @@ -11,7 +11,6 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/wire" - "github.com/lightningnetwork/lnd/htlcswitch/hop" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/routerrpc" "github.com/lightningnetwork/lnd/lnwire" @@ -62,29 +61,6 @@ func openChannel(client LightningClient, paymentHash, destination []byte, incomi return channelPoint, err } -func getChannel(ctx context.Context, client lnrpc.LightningClient, node []byte, channelPoint string) (uint64, uint64) { - r, err := client.ListChannels(ctx, &lnrpc.ListChannelsRequest{Peer: node}) - if err != nil { - log.Printf("client.ListChannels(%x) error: %v", node, err) - return 0, 0 - } - for _, c := range r.Channels { - log.Printf("getChannel(%x): %v", node, c.ChanId) - if c.ChannelPoint == channelPoint && c.Active { - confirmedChanId := c.ChanId - if c.ZeroConf { - confirmedChanId = c.ZeroConfConfirmedScid - if confirmedChanId == hop.Source.ToUint64() { - confirmedChanId = 0 - } - } - return c.ChanId, confirmedChanId - } - } - log.Printf("No channel found: getChannel(%x)", node) - return 0, 0 -} - func failForwardSend(interceptorClient routerrpc.Router_HtlcInterceptorClient, incomingCircuitKey *routerrpc.CircuitKey) { interceptorClient.Send(&routerrpc.ForwardHtlcInterceptResponse{ IncomingCircuitKey: incomingCircuitKey, @@ -228,7 +204,7 @@ func intercept(client *LndClient) { go resumeOrCancel( clientCtx, interceptorClient, request.IncomingCircuitKey, destination, - channelPoint.String(), uint64(amt), onionBlob.Bytes(), + *channelPoint, uint64(amt), onionBlob.Bytes(), ) } else { @@ -249,22 +225,27 @@ func resumeOrCancel( interceptorClient routerrpc.Router_HtlcInterceptorClient, incomingCircuitKey *routerrpc.CircuitKey, destination []byte, - channelPoint string, + channelPoint wire.OutPoint, outgoingAmountMsat uint64, onionBlob []byte, ) { deadline := time.Now().Add(10 * time.Second) for { - initialChanID, confirmedChanID := getChannel(ctx, client.client, destination, channelPoint) - if initialChanID != 0 { + ch, err := client.GetChannel(destination, channelPoint) + if err != nil { + failForwardSend(interceptorClient, incomingCircuitKey) + return + } + + if uint64(ch.InitialChannelID) != 0 { interceptorClient.Send(&routerrpc.ForwardHtlcInterceptResponse{ IncomingCircuitKey: incomingCircuitKey, Action: routerrpc.ResolveHoldForwardAction_RESUME, OutgoingAmountMsat: outgoingAmountMsat, - OutgoingRequestedChanId: initialChanID, + OutgoingRequestedChanId: uint64(ch.InitialChannelID), OnionBlob: onionBlob, }) - err := insertChannel(initialChanID, confirmedChanID, channelPoint, destination, time.Now()) + err := insertChannel(uint64(ch.InitialChannelID), uint64(ch.ConfirmedChannelID), channelPoint.String(), destination, time.Now()) if err != nil { log.Printf("insertChannel error: %v", err) } From c738f817acb5bafda395a9245c5529bb86ce719f Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Thu, 17 Nov 2022 15:38:33 +0100 Subject: [PATCH 082/214] Move main method to seperate file --- main.go | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++ server.go | 42 ------------------------------------------ 2 files changed, 51 insertions(+), 42 deletions(-) create mode 100644 main.go diff --git a/main.go b/main.go new file mode 100644 index 0000000..749f300 --- /dev/null +++ b/main.go @@ -0,0 +1,51 @@ +package main + +import ( + "fmt" + "log" + "os" + + "github.com/btcsuite/btcd/btcec/v2" +) + +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) + } + + client = NewLndClient() + + info, err := client.GetInfo() + if err != nil { + log.Fatalf("client.GetInfo() error: %v", err) + } + if nodeName == "" { + nodeName = info.Alias + } + if nodePubkey == "" { + nodePubkey = info.Pubkey + } + + go intercept(client) + + go forwardingHistorySynchronize(client) + go channelsSynchronize(client) + + s := NewGrpcServer() + err = s.Start() + if err != nil { + log.Fatalf("%v", err) + } + + log.Printf("lspd exited") +} diff --git a/server.go b/server.go index 2577e04..9d2de8e 100644 --- a/server.go +++ b/server.go @@ -304,45 +304,3 @@ func (s *server) Stop() { srv.GracefulStop() } } - -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) - } - - client = NewLndClient() - - info, err := client.GetInfo() - if err != nil { - log.Fatalf("client.GetInfo() error: %v", err) - } - if nodeName == "" { - nodeName = info.Alias - } - if nodePubkey == "" { - nodePubkey = info.Pubkey - } - - go intercept(client) - - go forwardingHistorySynchronize(client) - go channelsSynchronize(client) - - s := NewGrpcServer() - err = s.Start() - if err != nil { - log.Fatalf("%v", err) - } - - log.Printf("lspd exited") -} From 3d6d5bfb9d3990ad22dfa3e70c52555fe35e2a05 Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Thu, 17 Nov 2022 16:23:27 +0100 Subject: [PATCH 083/214] Pull out generic interception logic --- htlc_interceptor.go | 6 + intercept.go | 352 ++++++++++++++++++-------------------------- lnd_interceptor.go | 143 ++++++++++++++++++ main.go | 3 +- 4 files changed, 293 insertions(+), 211 deletions(-) create mode 100644 htlc_interceptor.go create mode 100644 lnd_interceptor.go diff --git a/htlc_interceptor.go b/htlc_interceptor.go new file mode 100644 index 0000000..bb487df --- /dev/null +++ b/htlc_interceptor.go @@ -0,0 +1,6 @@ +package main + +type HtlcInterceptor interface { + Start() error + Stop() error +} diff --git a/intercept.go b/intercept.go index f9a2b95..e1f39e0 100644 --- a/intercept.go +++ b/intercept.go @@ -2,27 +2,159 @@ package main import ( "bytes" - "context" "fmt" "log" "math/big" - "os" - "time" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/wire" - "github.com/lightningnetwork/lnd/lnrpc" - "github.com/lightningnetwork/lnd/lnrpc/routerrpc" + sphinx "github.com/lightningnetwork/lightning-onion" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/record" "github.com/lightningnetwork/lnd/routing/route" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/metadata" - "google.golang.org/grpc/status" - - sphinx "github.com/lightningnetwork/lightning-onion" ) +type interceptAction int + +const ( + INTERCEPT_RESUME interceptAction = 0 + INTERCEPT_RESUME_OR_CANCEL interceptAction = 1 + INTERCEPT_FAIL_HTLC interceptAction = 2 +) + +type interceptFailureCode int + +const ( + FAILURE_TEMPORARY_CHANNEL_FAILURE interceptFailureCode = 0 + FAILURE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS interceptFailureCode = 1 +) + +type interceptResult struct { + action interceptAction + failureCode interceptFailureCode + destination []byte + amountMsat uint64 + channelPoint *wire.OutPoint + onionBlob []byte +} + +func intercept(reqPaymentHash []byte, reqOutgoingAmountMsat uint64, reqOutgoingExpiry uint32) interceptResult { + paymentHash, paymentSecret, destination, incomingAmountMsat, outgoingAmountMsat, channelPoint, err := paymentInfo(reqPaymentHash) + if err != nil { + log.Printf("paymentInfo(%x) error: %v", reqPaymentHash, err) + return interceptResult{ + action: INTERCEPT_FAIL_HTLC, + } + } + log.Printf("paymentHash:%x\npaymentSecret:%x\ndestination:%x\nincomingAmountMsat:%v\noutgoingAmountMsat:%v\n\n", + paymentHash, paymentSecret, destination, incomingAmountMsat, outgoingAmountMsat) + if paymentSecret != nil { + + if channelPoint == nil { + if bytes.Compare(paymentHash, reqPaymentHash) == 0 { + channelPoint, err = openChannel(client, reqPaymentHash, destination, incomingAmountMsat) + log.Printf("openChannel(%x, %v) err: %v", destination, incomingAmountMsat, err) + if err != nil { + return interceptResult{ + action: INTERCEPT_FAIL_HTLC, + } + } + } else { //probing + failureCode := FAILURE_TEMPORARY_CHANNEL_FAILURE + isConnected, _ := client.IsConnected(destination) + if err != nil || !*isConnected { + failureCode = FAILURE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS + } + + return interceptResult{ + action: INTERCEPT_FAIL_HTLC, + failureCode: failureCode, + } + } + } + + pubKey, err := btcec.ParsePubKey(destination) + if err != nil { + log.Printf("btcec.ParsePubKey(%x): %v", destination, err) + return interceptResult{ + action: INTERCEPT_FAIL_HTLC, + } + } + + sessionKey, err := btcec.NewPrivateKey() + if err != nil { + log.Printf("btcec.NewPrivateKey(): %v", err) + return interceptResult{ + action: INTERCEPT_FAIL_HTLC, + } + } + + var bigProd, bigAmt big.Int + amt := (bigAmt.Div(bigProd.Mul(big.NewInt(outgoingAmountMsat), big.NewInt(int64(reqOutgoingAmountMsat))), big.NewInt(incomingAmountMsat))).Int64() + + var addr [32]byte + copy(addr[:], paymentSecret) + hop := route.Hop{ + AmtToForward: lnwire.MilliSatoshi(amt), + OutgoingTimeLock: reqOutgoingExpiry, + MPP: record.NewMPP(lnwire.MilliSatoshi(outgoingAmountMsat), addr), + CustomRecords: make(record.CustomSet), + } + + var b bytes.Buffer + err = hop.PackHopPayload(&b, uint64(0)) + if err != nil { + log.Printf("hop.PackHopPayload(): %v", err) + return interceptResult{ + action: INTERCEPT_FAIL_HTLC, + } + } + + payload, err := sphinx.NewHopPayload(nil, b.Bytes()) + if err != nil { + log.Printf("sphinx.NewHopPayload(): %v", err) + return interceptResult{ + action: INTERCEPT_FAIL_HTLC, + } + } + + var sphinxPath sphinx.PaymentPath + sphinxPath[0] = sphinx.OnionHop{ + NodePub: *pubKey, + HopPayload: payload, + } + sphinxPacket, err := sphinx.NewOnionPacket( + &sphinxPath, sessionKey, reqPaymentHash, + sphinx.DeterministicPacketFiller, + ) + if err != nil { + log.Printf("sphinx.NewOnionPacket(): %v", err) + return interceptResult{ + action: INTERCEPT_FAIL_HTLC, + } + } + var onionBlob bytes.Buffer + err = sphinxPacket.Encode(&onionBlob) + if err != nil { + log.Printf("sphinxPacket.Encode(): %v", err) + return interceptResult{ + action: INTERCEPT_FAIL_HTLC, + } + } + + return interceptResult{ + action: INTERCEPT_RESUME_OR_CANCEL, + destination: destination, + channelPoint: channelPoint, + amountMsat: uint64(amt), + onionBlob: onionBlob.Bytes(), + } + } else { + return interceptResult{ + action: INTERCEPT_RESUME, + } + } +} func checkPayment(incomingAmountMsat, outgoingAmountMsat int64) error { fees := incomingAmountMsat * channelFeePermyriad / 10_000 / 1_000 * 1_000 if fees < channelMinimumFeeMsat { @@ -60,203 +192,3 @@ func openChannel(client LightningClient, paymentHash, destination []byte, incomi err = setFundingTx(paymentHash, channelPoint) return channelPoint, err } - -func failForwardSend(interceptorClient routerrpc.Router_HtlcInterceptorClient, incomingCircuitKey *routerrpc.CircuitKey) { - interceptorClient.Send(&routerrpc.ForwardHtlcInterceptResponse{ - IncomingCircuitKey: incomingCircuitKey, - Action: routerrpc.ResolveHoldForwardAction_FAIL, - }) -} - -func intercept(client *LndClient) { - for { - cancellableCtx, cancel := context.WithCancel(context.Background()) - clientCtx := metadata.AppendToOutgoingContext(cancellableCtx, "macaroon", os.Getenv("LND_MACAROON_HEX")) - interceptorClient, err := client.routerClient.HtlcInterceptor(clientCtx) - if err != nil { - log.Printf("routerClient.HtlcInterceptor(): %v", err) - cancel() - time.Sleep(1 * time.Second) - continue - } - - for { - request, err := interceptorClient.Recv() - if err != nil { - // If it is just the error result of the context cancellation - // the we exit silently. - status, ok := status.FromError(err) - if ok && status.Code() == codes.Canceled { - break - } - // Otherwise it an unexpected error, we fail the test. - log.Printf("unexpected error in interceptor.Recv() %v", err) - cancel() - break - } - fmt.Printf("htlc: %v\nchanID: %v\nincoming amount: %v\noutgoing amount: %v\nincomin expiry: %v\noutgoing expiry: %v\npaymentHash: %x\nonionBlob: %x\n\n", - request.IncomingCircuitKey.HtlcId, - request.IncomingCircuitKey.ChanId, - request.IncomingAmountMsat, - request.OutgoingAmountMsat, - request.IncomingExpiry, - request.OutgoingExpiry, - request.PaymentHash, - request.OnionBlob, - ) - - paymentHash, paymentSecret, destination, incomingAmountMsat, outgoingAmountMsat, channelPoint, err := paymentInfo(request.PaymentHash) - if err != nil { - log.Printf("paymentInfo(%x) error: %v", request.PaymentHash, err) - failForwardSend(interceptorClient, request.IncomingCircuitKey) - continue - } - log.Printf("paymentHash:%x\npaymentSecret:%x\ndestination:%x\nincomingAmountMsat:%v\noutgoingAmountMsat:%v\n\n", - paymentHash, paymentSecret, destination, incomingAmountMsat, outgoingAmountMsat) - if paymentSecret != nil { - - if channelPoint == nil { - if bytes.Compare(paymentHash, request.PaymentHash) == 0 { - channelPoint, err = openChannel(client, request.PaymentHash, destination, incomingAmountMsat) - log.Printf("openChannel(%x, %v) err: %v", destination, incomingAmountMsat, err) - if err != nil { - failForwardSend(interceptorClient, request.IncomingCircuitKey) - continue - } - } else { //probing - failureCode := lnrpc.Failure_TEMPORARY_CHANNEL_FAILURE - isConnected, _ := client.IsConnected(destination) - if err != nil || !*isConnected { - failureCode = lnrpc.Failure_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS - } - - interceptorClient.Send(&routerrpc.ForwardHtlcInterceptResponse{ - IncomingCircuitKey: request.IncomingCircuitKey, - Action: routerrpc.ResolveHoldForwardAction_FAIL, - FailureCode: failureCode, - }) - continue - } - } - - pubKey, err := btcec.ParsePubKey(destination) - if err != nil { - log.Printf("btcec.ParsePubKey(%x): %v", destination, err) - failForwardSend(interceptorClient, request.IncomingCircuitKey) - continue - } - - sessionKey, err := btcec.NewPrivateKey() - if err != nil { - log.Printf("btcec.NewPrivateKey(): %v", err) - failForwardSend(interceptorClient, request.IncomingCircuitKey) - continue - } - - var bigProd, bigAmt big.Int - amt := (bigAmt.Div(bigProd.Mul(big.NewInt(outgoingAmountMsat), big.NewInt(int64(request.OutgoingAmountMsat))), big.NewInt(incomingAmountMsat))).Int64() - - var addr [32]byte - copy(addr[:], paymentSecret) - hop := route.Hop{ - AmtToForward: lnwire.MilliSatoshi(amt), - OutgoingTimeLock: request.OutgoingExpiry, - MPP: record.NewMPP(lnwire.MilliSatoshi(outgoingAmountMsat), addr), - CustomRecords: make(record.CustomSet), - } - - var b bytes.Buffer - err = hop.PackHopPayload(&b, uint64(0)) - if err != nil { - log.Printf("hop.PackHopPayload(): %v", err) - failForwardSend(interceptorClient, request.IncomingCircuitKey) - continue - } - - payload, err := sphinx.NewHopPayload(nil, b.Bytes()) - if err != nil { - log.Printf("sphinx.NewHopPayload(): %v", err) - failForwardSend(interceptorClient, request.IncomingCircuitKey) - continue - } - - var sphinxPath sphinx.PaymentPath - sphinxPath[0] = sphinx.OnionHop{ - NodePub: *pubKey, - HopPayload: payload, - } - sphinxPacket, err := sphinx.NewOnionPacket( - &sphinxPath, sessionKey, request.PaymentHash, - sphinx.DeterministicPacketFiller, - ) - if err != nil { - log.Printf("sphinx.NewOnionPacket(): %v", err) - failForwardSend(interceptorClient, request.IncomingCircuitKey) - continue - } - var onionBlob bytes.Buffer - err = sphinxPacket.Encode(&onionBlob) - if err != nil { - log.Printf("sphinxPacket.Encode(): %v", err) - failForwardSend(interceptorClient, request.IncomingCircuitKey) - continue - } - - go resumeOrCancel( - clientCtx, interceptorClient, request.IncomingCircuitKey, destination, - *channelPoint, uint64(amt), onionBlob.Bytes(), - ) - - } else { - interceptorClient.Send(&routerrpc.ForwardHtlcInterceptResponse{ - IncomingCircuitKey: request.IncomingCircuitKey, - Action: routerrpc.ResolveHoldForwardAction_RESUME, - OutgoingAmountMsat: request.OutgoingAmountMsat, - OutgoingRequestedChanId: request.OutgoingRequestedChanId, - OnionBlob: request.OnionBlob, - }) - } - } - } -} - -func resumeOrCancel( - ctx context.Context, - interceptorClient routerrpc.Router_HtlcInterceptorClient, - incomingCircuitKey *routerrpc.CircuitKey, - destination []byte, - channelPoint wire.OutPoint, - outgoingAmountMsat uint64, - onionBlob []byte, -) { - deadline := time.Now().Add(10 * time.Second) - for { - ch, err := client.GetChannel(destination, channelPoint) - if err != nil { - failForwardSend(interceptorClient, incomingCircuitKey) - return - } - - if uint64(ch.InitialChannelID) != 0 { - interceptorClient.Send(&routerrpc.ForwardHtlcInterceptResponse{ - IncomingCircuitKey: incomingCircuitKey, - Action: routerrpc.ResolveHoldForwardAction_RESUME, - OutgoingAmountMsat: outgoingAmountMsat, - OutgoingRequestedChanId: uint64(ch.InitialChannelID), - OnionBlob: onionBlob, - }) - err := insertChannel(uint64(ch.InitialChannelID), uint64(ch.ConfirmedChannelID), channelPoint.String(), destination, time.Now()) - if err != nil { - log.Printf("insertChannel error: %v", err) - } - return - } - log.Printf("getChannel(%x, %v) returns 0", destination, channelPoint) - if time.Now().After(deadline) { - log.Printf("Stop retrying getChannel(%x, %v)", destination, channelPoint) - break - } - time.Sleep(1 * time.Second) - } - failForwardSend(interceptorClient, incomingCircuitKey) -} diff --git a/lnd_interceptor.go b/lnd_interceptor.go new file mode 100644 index 0000000..78389fe --- /dev/null +++ b/lnd_interceptor.go @@ -0,0 +1,143 @@ +package main + +import ( + "context" + "fmt" + "log" + "os" + "time" + + "github.com/btcsuite/btcd/wire" + "github.com/lightningnetwork/lnd/lnrpc/routerrpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" +) + +type LndHtlcInterceptor struct { + client *LndClient +} + +func NewLndHtlcInterceptor(client *LndClient) *LndHtlcInterceptor { + return &LndHtlcInterceptor{ + client: client, + } +} + +func (i *LndHtlcInterceptor) Start() error { + go forwardingHistorySynchronize(i.client) + go channelsSynchronize(i.client) + return i.intercept() +} + +func (i *LndHtlcInterceptor) Stop() error { + return nil +} + +func (i *LndHtlcInterceptor) intercept() error { + for { + cancellableCtx, cancel := context.WithCancel(context.Background()) + clientCtx := metadata.AppendToOutgoingContext(cancellableCtx, "macaroon", os.Getenv("LND_MACAROON_HEX")) + interceptorClient, err := client.routerClient.HtlcInterceptor(clientCtx) + if err != nil { + log.Printf("routerClient.HtlcInterceptor(): %v", err) + cancel() + time.Sleep(1 * time.Second) + continue + } + + for { + request, err := interceptorClient.Recv() + if err != nil { + // If it is just the error result of the context cancellation + // the we exit silently. + status, ok := status.FromError(err) + if ok && status.Code() == codes.Canceled { + break + } + // Otherwise it an unexpected error, we fail the test. + log.Printf("unexpected error in interceptor.Recv() %v", err) + cancel() + break + } + fmt.Printf("htlc: %v\nchanID: %v\nincoming amount: %v\noutgoing amount: %v\nincomin expiry: %v\noutgoing expiry: %v\npaymentHash: %x\nonionBlob: %x\n\n", + request.IncomingCircuitKey.HtlcId, + request.IncomingCircuitKey.ChanId, + request.IncomingAmountMsat, + request.OutgoingAmountMsat, + request.IncomingExpiry, + request.OutgoingExpiry, + request.PaymentHash, + request.OnionBlob, + ) + + interceptResult := intercept(request.PaymentHash, request.OutgoingAmountMsat, request.OutgoingExpiry) + switch interceptResult.action { + case INTERCEPT_RESUME_OR_CANCEL: + go resumeOrCancel(clientCtx, interceptorClient, request.IncomingCircuitKey, + interceptResult.destination, *interceptResult.channelPoint, + interceptResult.amountMsat, interceptResult.onionBlob) + case INTERCEPT_FAIL_HTLC: + failForwardSend(interceptorClient, request.IncomingCircuitKey) + case INTERCEPT_RESUME: + fallthrough + default: + interceptorClient.Send(&routerrpc.ForwardHtlcInterceptResponse{ + IncomingCircuitKey: request.IncomingCircuitKey, + Action: routerrpc.ResolveHoldForwardAction_RESUME, + OutgoingAmountMsat: request.OutgoingAmountMsat, + OutgoingRequestedChanId: request.OutgoingRequestedChanId, + OnionBlob: request.OnionBlob, + }) + } + } + } +} + +func failForwardSend(interceptorClient routerrpc.Router_HtlcInterceptorClient, incomingCircuitKey *routerrpc.CircuitKey) { + interceptorClient.Send(&routerrpc.ForwardHtlcInterceptResponse{ + IncomingCircuitKey: incomingCircuitKey, + Action: routerrpc.ResolveHoldForwardAction_FAIL, + }) +} + +func resumeOrCancel( + ctx context.Context, + interceptorClient routerrpc.Router_HtlcInterceptorClient, + incomingCircuitKey *routerrpc.CircuitKey, + destination []byte, + channelPoint wire.OutPoint, + outgoingAmountMsat uint64, + onionBlob []byte, +) { + deadline := time.Now().Add(10 * time.Second) + for { + ch, err := client.GetChannel(destination, channelPoint) + if err != nil { + failForwardSend(interceptorClient, incomingCircuitKey) + return + } + + if uint64(ch.InitialChannelID) != 0 { + interceptorClient.Send(&routerrpc.ForwardHtlcInterceptResponse{ + IncomingCircuitKey: incomingCircuitKey, + Action: routerrpc.ResolveHoldForwardAction_RESUME, + OutgoingAmountMsat: outgoingAmountMsat, + OutgoingRequestedChanId: uint64(ch.InitialChannelID), + OnionBlob: onionBlob, + }) + err := insertChannel(uint64(ch.InitialChannelID), uint64(ch.ConfirmedChannelID), channelPoint.String(), destination, time.Now()) + if err != nil { + log.Printf("insertChannel error: %v", err) + } + return + } + log.Printf("getChannel(%x, %v) returns 0", destination, channelPoint) + if time.Now().After(deadline) { + log.Printf("Stop retrying getChannel(%x, %v)", destination, channelPoint) + break + } + time.Sleep(1 * time.Second) + } + failForwardSend(interceptorClient, incomingCircuitKey) +} diff --git a/main.go b/main.go index 749f300..7c06105 100644 --- a/main.go +++ b/main.go @@ -24,6 +24,7 @@ func main() { } client = NewLndClient() + interceptor := NewLndHtlcInterceptor(client) info, err := client.GetInfo() if err != nil { @@ -36,7 +37,7 @@ func main() { nodePubkey = info.Pubkey } - go intercept(client) + go interceptor.Start() go forwardingHistorySynchronize(client) go channelsSynchronize(client) From 5cfcd41f32bef97ceaa16c498d897820b4f0fbfc Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Thu, 17 Nov 2022 17:01:31 +0100 Subject: [PATCH 084/214] bytes.Equal instead of compare --- intercept.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/intercept.go b/intercept.go index e1f39e0..f8ed8f4 100644 --- a/intercept.go +++ b/intercept.go @@ -51,7 +51,7 @@ func intercept(reqPaymentHash []byte, reqOutgoingAmountMsat uint64, reqOutgoingE if paymentSecret != nil { if channelPoint == nil { - if bytes.Compare(paymentHash, reqPaymentHash) == 0 { + if bytes.Equal(paymentHash, reqPaymentHash) { channelPoint, err = openChannel(client, reqPaymentHash, destination, incomingAmountMsat) log.Printf("openChannel(%x, %v) err: %v", destination, incomingAmountMsat, err) if err != nil { From c1a17bc296e415ba33cf0b8243b2f0a7e607da4a Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Thu, 17 Nov 2022 17:11:51 +0100 Subject: [PATCH 085/214] A little prettier start and stop --- lnd_interceptor.go | 14 +++++++++++--- main.go | 35 ++++++++++++++++++++++++++++------- 2 files changed, 39 insertions(+), 10 deletions(-) diff --git a/lnd_interceptor.go b/lnd_interceptor.go index 78389fe..45b9089 100644 --- a/lnd_interceptor.go +++ b/lnd_interceptor.go @@ -15,7 +15,8 @@ import ( ) type LndHtlcInterceptor struct { - client *LndClient + client *LndClient + stopRequested bool } func NewLndHtlcInterceptor(client *LndClient) *LndHtlcInterceptor { @@ -30,12 +31,16 @@ func (i *LndHtlcInterceptor) Start() error { return i.intercept() } -func (i *LndHtlcInterceptor) Stop() error { - return nil +func (i *LndHtlcInterceptor) Stop() { + i.stopRequested = true } func (i *LndHtlcInterceptor) intercept() error { for { + if i.stopRequested { + return nil + } + cancellableCtx, cancel := context.WithCancel(context.Background()) clientCtx := metadata.AppendToOutgoingContext(cancellableCtx, "macaroon", os.Getenv("LND_MACAROON_HEX")) interceptorClient, err := client.routerClient.HtlcInterceptor(clientCtx) @@ -47,6 +52,9 @@ func (i *LndHtlcInterceptor) intercept() error { } for { + if i.stopRequested { + return nil + } request, err := interceptorClient.Recv() if err != nil { // If it is just the error result of the context cancellation diff --git a/main.go b/main.go index 7c06105..1a89553 100644 --- a/main.go +++ b/main.go @@ -4,6 +4,7 @@ import ( "fmt" "log" "os" + "sync" "github.com/btcsuite/btcd/btcec/v2" ) @@ -25,6 +26,7 @@ func main() { client = NewLndClient() interceptor := NewLndHtlcInterceptor(client) + s := NewGrpcServer() info, err := client.GetInfo() if err != nil { @@ -37,16 +39,35 @@ func main() { nodePubkey = info.Pubkey } - go interceptor.Start() - go forwardingHistorySynchronize(client) go channelsSynchronize(client) - s := NewGrpcServer() - err = s.Start() - if err != nil { - log.Fatalf("%v", err) - } + var wg sync.WaitGroup + wg.Add(2) + go func() { + err := interceptor.Start() + if err == nil { + log.Printf("Interceptor stopped.") + } else { + log.Printf("FATAL. Interceptor stopped with error: %v", err) + } + s.Stop() + wg.Done() + }() + + go func() { + err := s.Start() + if err == nil { + log.Printf("GRPC server stopped.") + } else { + log.Printf("FATAL. GRPC server stopped with error: %v", err) + } + + interceptor.Stop() + wg.Done() + }() + + wg.Wait() log.Printf("lspd exited") } From 9531c713c1870d91204893168865e8c3d47717ec Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Fri, 18 Nov 2022 09:55:38 +0100 Subject: [PATCH 086/214] cancel on all code paths --- lnd_interceptor.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lnd_interceptor.go b/lnd_interceptor.go index 45b9089..6c9f319 100644 --- a/lnd_interceptor.go +++ b/lnd_interceptor.go @@ -53,6 +53,7 @@ func (i *LndHtlcInterceptor) intercept() error { for { if i.stopRequested { + cancel() return nil } request, err := interceptorClient.Recv() @@ -99,6 +100,8 @@ func (i *LndHtlcInterceptor) intercept() error { }) } } + + cancel() } } From 6ad2f71edc3e03d36b9c6cc325ffff2b1b0d7a6b Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Fri, 18 Nov 2022 16:33:01 +0100 Subject: [PATCH 087/214] add intercept with code --- intercept.go | 17 +++++++++-------- lnd_interceptor.go | 39 ++++++++++++++++++++++++++------------- 2 files changed, 35 insertions(+), 21 deletions(-) diff --git a/intercept.go b/intercept.go index f8ed8f4..dd5b98f 100644 --- a/intercept.go +++ b/intercept.go @@ -17,16 +17,17 @@ import ( type interceptAction int const ( - INTERCEPT_RESUME interceptAction = 0 - INTERCEPT_RESUME_OR_CANCEL interceptAction = 1 - INTERCEPT_FAIL_HTLC interceptAction = 2 + INTERCEPT_RESUME interceptAction = 0 + INTERCEPT_RESUME_OR_CANCEL interceptAction = 1 + INTERCEPT_FAIL_HTLC interceptAction = 2 + INTERCEPT_FAIL_HTLC_WITH_CODE interceptAction = 3 ) -type interceptFailureCode int +type interceptFailureCode uint16 -const ( - FAILURE_TEMPORARY_CHANNEL_FAILURE interceptFailureCode = 0 - FAILURE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS interceptFailureCode = 1 +var ( + FAILURE_TEMPORARY_CHANNEL_FAILURE interceptFailureCode = 0x1007 + FAILURE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS interceptFailureCode = 0x4015 ) type interceptResult struct { @@ -67,7 +68,7 @@ func intercept(reqPaymentHash []byte, reqOutgoingAmountMsat uint64, reqOutgoingE } return interceptResult{ - action: INTERCEPT_FAIL_HTLC, + action: INTERCEPT_FAIL_HTLC_WITH_CODE, failureCode: failureCode, } } diff --git a/lnd_interceptor.go b/lnd_interceptor.go index 6c9f319..a5e5434 100644 --- a/lnd_interceptor.go +++ b/lnd_interceptor.go @@ -7,7 +7,7 @@ import ( "os" "time" - "github.com/btcsuite/btcd/wire" + "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/routerrpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/metadata" @@ -84,10 +84,15 @@ func (i *LndHtlcInterceptor) intercept() error { switch interceptResult.action { case INTERCEPT_RESUME_OR_CANCEL: go resumeOrCancel(clientCtx, interceptorClient, request.IncomingCircuitKey, - interceptResult.destination, *interceptResult.channelPoint, - interceptResult.amountMsat, interceptResult.onionBlob) + interceptResult) case INTERCEPT_FAIL_HTLC: failForwardSend(interceptorClient, request.IncomingCircuitKey) + case INTERCEPT_FAIL_HTLC_WITH_CODE: + interceptorClient.Send(&routerrpc.ForwardHtlcInterceptResponse{ + IncomingCircuitKey: request.IncomingCircuitKey, + Action: routerrpc.ResolveHoldForwardAction_FAIL, + FailureCode: mapFailureCode(interceptResult.failureCode), + }) case INTERCEPT_RESUME: fallthrough default: @@ -105,6 +110,17 @@ func (i *LndHtlcInterceptor) intercept() error { } } +func mapFailureCode(original interceptFailureCode) lnrpc.Failure_FailureCode { + switch original { + case FAILURE_TEMPORARY_CHANNEL_FAILURE: + return lnrpc.Failure_TEMPORARY_CHANNEL_FAILURE + case FAILURE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS: + return lnrpc.Failure_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS + default: + return lnrpc.Failure_TEMPORARY_CHANNEL_FAILURE + } +} + func failForwardSend(interceptorClient routerrpc.Router_HtlcInterceptorClient, incomingCircuitKey *routerrpc.CircuitKey) { interceptorClient.Send(&routerrpc.ForwardHtlcInterceptResponse{ IncomingCircuitKey: incomingCircuitKey, @@ -116,14 +132,11 @@ func resumeOrCancel( ctx context.Context, interceptorClient routerrpc.Router_HtlcInterceptorClient, incomingCircuitKey *routerrpc.CircuitKey, - destination []byte, - channelPoint wire.OutPoint, - outgoingAmountMsat uint64, - onionBlob []byte, + interceptResult interceptResult, ) { deadline := time.Now().Add(10 * time.Second) for { - ch, err := client.GetChannel(destination, channelPoint) + ch, err := client.GetChannel(interceptResult.destination, *interceptResult.channelPoint) if err != nil { failForwardSend(interceptorClient, incomingCircuitKey) return @@ -133,19 +146,19 @@ func resumeOrCancel( interceptorClient.Send(&routerrpc.ForwardHtlcInterceptResponse{ IncomingCircuitKey: incomingCircuitKey, Action: routerrpc.ResolveHoldForwardAction_RESUME, - OutgoingAmountMsat: outgoingAmountMsat, + OutgoingAmountMsat: interceptResult.amountMsat, OutgoingRequestedChanId: uint64(ch.InitialChannelID), - OnionBlob: onionBlob, + OnionBlob: interceptResult.onionBlob, }) - err := insertChannel(uint64(ch.InitialChannelID), uint64(ch.ConfirmedChannelID), channelPoint.String(), destination, time.Now()) + err := insertChannel(uint64(ch.InitialChannelID), uint64(ch.ConfirmedChannelID), interceptResult.channelPoint.String(), interceptResult.destination, time.Now()) if err != nil { log.Printf("insertChannel error: %v", err) } return } - log.Printf("getChannel(%x, %v) returns 0", destination, channelPoint) + log.Printf("getChannel(%x, %v) returns 0", interceptResult.destination, interceptResult.channelPoint.String()) if time.Now().After(deadline) { - log.Printf("Stop retrying getChannel(%x, %v)", destination, channelPoint) + log.Printf("Stop retrying getChannel(%x, %v)", interceptResult.destination, interceptResult.channelPoint.String()) break } time.Sleep(1 * time.Second) From 2de54bf2dd2c97a6c2292003881f658338a307d0 Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Fri, 18 Nov 2022 16:41:18 +0100 Subject: [PATCH 088/214] properly implement stop and client lnd intercept --- lnd_interceptor.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lnd_interceptor.go b/lnd_interceptor.go index a5e5434..2683f8b 100644 --- a/lnd_interceptor.go +++ b/lnd_interceptor.go @@ -31,8 +31,9 @@ func (i *LndHtlcInterceptor) Start() error { return i.intercept() } -func (i *LndHtlcInterceptor) Stop() { +func (i *LndHtlcInterceptor) Stop() error { i.stopRequested = true + return nil } func (i *LndHtlcInterceptor) intercept() error { @@ -43,7 +44,7 @@ func (i *LndHtlcInterceptor) intercept() error { cancellableCtx, cancel := context.WithCancel(context.Background()) clientCtx := metadata.AppendToOutgoingContext(cancellableCtx, "macaroon", os.Getenv("LND_MACAROON_HEX")) - interceptorClient, err := client.routerClient.HtlcInterceptor(clientCtx) + interceptorClient, err := i.client.routerClient.HtlcInterceptor(clientCtx) if err != nil { log.Printf("routerClient.HtlcInterceptor(): %v", err) cancel() From 5b864f9cce70fd101b6d8e2f340a8a1b64f442db Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Fri, 18 Nov 2022 16:33:53 +0100 Subject: [PATCH 089/214] implement cln client and interceptor --- cln_client.go | 198 ++++++++++++++++++++++++++++++++++++++++++++ cln_interceptor.go | 191 ++++++++++++++++++++++++++++++++++++++++++ go.mod | 8 +- short_channel_id.go | 39 +++++---- 4 files changed, 416 insertions(+), 20 deletions(-) create mode 100644 cln_client.go create mode 100644 cln_interceptor.go diff --git a/cln_client.go b/cln_client.go new file mode 100644 index 0000000..610c0d5 --- /dev/null +++ b/cln_client.go @@ -0,0 +1,198 @@ +package main + +import ( + "encoding/hex" + "fmt" + "log" + + "github.com/btcsuite/btcd/wire" + "github.com/niftynei/glightning/glightning" + "golang.org/x/exp/slices" +) + +type ClnClient struct { + client *glightning.Lightning +} + +var ( + OPEN_STATUSES = []string{"CHANNELD_NORMAL"} + PENDING_STATUSES = []string{"OPENINGD", "CHANNELD_AWAITING_LOCKIN"} + CLOSING_STATUSES = []string{"CHANNELD_SHUTTING_DOWN", "CLOSINGD_SIGEXCHANGE", "CLOSINGD_COMPLETE", "AWAITING_UNILATERAL", "FUNDING_SPEND_SEEN", "ONCHAIN"} + CLOSED_STATUSES = []string{"CLOSED"} +) + +func NewClnClient(rpcFile string, lightningDir string) *ClnClient { + client := glightning.NewLightning() + client.SetTimeout(60) + client.StartUp(rpcFile, lightningDir) + return &ClnClient{ + client: client, + } +} + +func (c *ClnClient) GetInfo() (*GetInfoResult, error) { + info, err := c.client.GetInfo() + if err != nil { + log.Printf("CLN: client.GetInfo() error: %v", err) + return nil, err + } + + return &GetInfoResult{ + Alias: info.Alias, + Pubkey: info.Id, + }, nil +} + +func (c *ClnClient) IsConnected(destination []byte) (*bool, error) { + pubKey := hex.EncodeToString(destination) + peers, err := c.client.ListPeers() + if err != nil { + log.Printf("CLN: client.ListPeers() error: %v", err) + return nil, fmt.Errorf("CLN: client.ListPeers() error: %w", err) + } + + for _, peer := range peers { + if pubKey == peer.Id { + log.Printf("destination online: %x", destination) + result := true + return &result, nil + } + } + + log.Printf("CLN: destination offline: %x", destination) + result := false + return &result, nil +} + +func (c *ClnClient) OpenChannel(req *OpenChannelRequest) (*wire.OutPoint, error) { + pubkey := hex.EncodeToString(req.Destination) + minConf := uint16(req.TargetConf) + if req.IsZeroConf { + minConf = 0 + } + + var minDepth *uint16 + if req.IsZeroConf { + var d uint16 = 0 + minDepth = &d + } + + fundResult, err := c.client.FundChannelExt( + pubkey, + glightning.NewSat(int(req.CapacitySat)), + &glightning.FeeRate{ + Directive: glightning.Slow, + }, + !req.IsPrivate, + &minConf, + glightning.NewMsat(0), + minDepth, + ) + + if err != nil { + log.Printf("CLN: client.FundChannelExt(%v, %v) error: %v", pubkey, req.CapacitySat, err) + return nil, err + } + + fundingTxId, err := hex.DecodeString(fundResult.FundingTxId) + if err != nil { + log.Printf("CLN: hex.DecodeString(%s) error: %v", fundResult.FundingTxId, err) + return nil, err + } + + channelPoint, err := NewOutPoint(fundingTxId, uint32(fundResult.FundingTxOutputNum)) + if err != nil { + log.Printf("CLN: NewOutPoint(%x, %d) error: %v", fundingTxId, fundResult.FundingTxOutputNum, err) + return nil, err + } + + return channelPoint, nil +} + +func (c *ClnClient) GetChannel(peerID []byte, channelPoint wire.OutPoint) (*GetChannelResult, error) { + pubkey := hex.EncodeToString(peerID) + peer, err := c.client.GetPeer(pubkey) + fundingTxID := channelPoint.Hash.String() + if err != nil { + log.Printf("CLN: client.GetPeer(%s) error: %v", pubkey, err) + return nil, err + } + + for _, c := range peer.Channels { + log.Printf("getChannel destination: %s, Short channel id: %v, local alias: %v , FundingTxID:%v, State:%v ", pubkey, c.ShortChannelId, c.Alias.Local, c.FundingTxId, c.State) + if slices.Contains(OPEN_STATUSES, c.State) && c.FundingTxId == fundingTxID { + confirmedChanID, err := NewShortChannelIDFromString(c.ShortChannelId) + if err != nil { + fmt.Printf("NewShortChannelIDFromString %v error: %v", c.ShortChannelId, err) + return nil, err + } + initialChanID, err := NewShortChannelIDFromString(c.Alias.Local) + if err != nil { + fmt.Printf("NewShortChannelIDFromString %v error: %v", c.Alias.Local, err) + return nil, err + } + return &GetChannelResult{ + InitialChannelID: *initialChanID, + ConfirmedChannelID: *confirmedChanID, + }, nil + } + } + + log.Printf("No channel found: getChannel(%v)", pubkey) + return nil, fmt.Errorf("no channel found") +} + +func (c *ClnClient) GetNodeChannelCount(nodeID []byte) (int, error) { + pubkey := hex.EncodeToString(nodeID) + peer, err := c.client.GetPeer(pubkey) + if err != nil { + log.Printf("CLN: client.GetPeer(%s) error: %v", pubkey, err) + return 0, err + } + + count := 0 + openPendingStatuses := append(OPEN_STATUSES, PENDING_STATUSES...) + for _, c := range peer.Channels { + if slices.Contains(openPendingStatuses, c.State) { + count++ + } + } + + return count, nil +} + +func (c *ClnClient) GetClosedChannels(nodeID string, channelPoints map[string]uint64) (map[string]uint64, error) { + r := make(map[string]uint64) + if len(channelPoints) == 0 { + return r, nil + } + + peer, err := c.client.GetPeer(nodeID) + if err != nil { + log.Printf("CLN: client.GetPeer(%s) error: %v", nodeID, err) + return nil, err + } + + lookup := make(map[string]uint64) + for _, c := range peer.Channels { + if slices.Contains(CLOSING_STATUSES, c.State) { + cid, err := NewShortChannelIDFromString(c.ShortChannelId) + if err != nil { + log.Printf("CLN: GetClosedChannels NewShortChannelIDFromString(%v) error: %v", c.ShortChannelId, err) + continue + } + + outnum := uint64(*cid) & 0xFFFFFF + cp := fmt.Sprintf("%s:%d", c.FundingTxId, outnum) + lookup[cp] = uint64(*cid) + } + } + + for c, h := range channelPoints { + if _, ok := lookup[c]; !ok { + r[c] = h + } + } + + return r, nil +} diff --git a/cln_interceptor.go b/cln_interceptor.go new file mode 100644 index 0000000..6414d56 --- /dev/null +++ b/cln_interceptor.go @@ -0,0 +1,191 @@ +package main + +import ( + "bytes" + "encoding/hex" + "fmt" + "io" + "log" + "os" + "time" + + sphinx "github.com/lightningnetwork/lightning-onion" + "github.com/lightningnetwork/lnd/record" + "github.com/lightningnetwork/lnd/tlv" + "github.com/niftynei/glightning/glightning" +) + +type ClnHtlcInterceptor struct { + client *ClnClient + plugin *glightning.Plugin +} + +func NewClnHtlcInterceptor(client *ClnClient) *ClnHtlcInterceptor { + return &ClnHtlcInterceptor{ + client: client, + } +} + +func (i *ClnHtlcInterceptor) Start() error { + //c-lightning plugin initiate + plugin := glightning.NewPlugin(onInit) + i.plugin = plugin + plugin.RegisterHooks(&glightning.Hooks{ + HtlcAccepted: i.OnHtlcAccepted, + }) + + err := plugin.Start(os.Stdin, os.Stdout) + if err != nil { + log.Printf("Plugin error: %v", err) + return err + } + + return nil +} + +func (i *ClnHtlcInterceptor) Stop() error { + plugin := i.plugin + if plugin != nil { + plugin.Stop() + } + + return nil +} + +func onInit(plugin *glightning.Plugin, options map[string]glightning.Option, config *glightning.Config) { + log.Printf("successfully init'd! %v\n", config.RpcFile) +} + +func (i *ClnHtlcInterceptor) OnHtlcAccepted(event *glightning.HtlcAcceptedEvent) (*glightning.HtlcAcceptedResponse, error) { + log.Printf("htlc_accepted called\n") + onion := event.Onion + + log.Printf("htlc: %v\nchanID: %v\nincoming amount: %v\noutgoing amount: %v\nincoming expiry: %v\noutgoing expiry: %v\npaymentHash: %v\nonionBlob: %v\n\n", + event.Htlc, + onion.ShortChannelId, + event.Htlc.AmountMilliSatoshi, //with fees + onion.ForwardAmount, + event.Htlc.CltvExpiryRelative, + event.Htlc.CltvExpiry, + event.Htlc.PaymentHash, + onion, + ) + + // fail htlc in case payment hash is not valid. + paymentHashBytes, err := hex.DecodeString(event.Htlc.PaymentHash) + if err != nil { + log.Printf("hex.DecodeString(%v) error: %v", event.Htlc.PaymentHash, err) + return event.Fail(uint16(FAILURE_TEMPORARY_CHANNEL_FAILURE)), nil + } + + interceptResult := intercept(paymentHashBytes, onion.ForwardAmount, uint32(event.Htlc.CltvExpiry)) + switch interceptResult.action { + case INTERCEPT_RESUME_OR_CANCEL: + return i.resumeOrCancel(event, interceptResult), nil + case INTERCEPT_FAIL_HTLC: + return event.Fail(uint16(FAILURE_TEMPORARY_CHANNEL_FAILURE)), nil + case INTERCEPT_FAIL_HTLC_WITH_CODE: + return event.Fail(uint16(interceptResult.failureCode)), nil + case INTERCEPT_RESUME: + fallthrough + default: + return event.Continue(), nil + } +} + +func (i *ClnHtlcInterceptor) resumeOrCancel(event *glightning.HtlcAcceptedEvent, interceptResult interceptResult) *glightning.HtlcAcceptedResponse { + deadline := time.Now().Add(60 * time.Second) + + for { + chanResult, _ := i.client.GetChannel(interceptResult.destination, *interceptResult.channelPoint) + if chanResult != nil { + log.Printf("channel opended successfully alias: %v, confirmed: %v", chanResult.InitialChannelID.ToString(), chanResult.ConfirmedChannelID.ToString()) + + err := insertChannel( + uint64(chanResult.InitialChannelID), + uint64(chanResult.ConfirmedChannelID), + interceptResult.channelPoint.String(), + interceptResult.destination, + time.Now(), + ) + + if err != nil { + log.Printf("insertChannel error: %v", err) + return event.Fail(uint16(FAILURE_TEMPORARY_CHANNEL_FAILURE)) + } + + channelID := uint64(chanResult.ConfirmedChannelID) + if channelID == 0 { + channelID = uint64(chanResult.InitialChannelID) + } + //decoding and encoding onion with alias in type 6 record. + newPayload, err := encodePayloadWithNextHop(event.Onion.Payload, channelID) + if err != nil { + log.Printf("encodePayloadWithNextHop error: %v", err) + return event.Fail(uint16(FAILURE_TEMPORARY_CHANNEL_FAILURE)) + } + + log.Printf("forwarding htlc to the destination node and a new private channel was opened") + return event.ContinueWithPayload(newPayload) + } + + log.Printf("waiting for channel to get opened.... %v\n", interceptResult.destination) + if time.Now().After(deadline) { + log.Printf("Stop retrying getChannel(%v, %v)", interceptResult.destination, interceptResult.channelPoint.String()) + break + } + time.Sleep(1 * time.Second) + } + log.Printf("Error: Channel failed to opened... timed out. ") + return event.Fail(uint16(FAILURE_TEMPORARY_CHANNEL_FAILURE)) +} + +func encodePayloadWithNextHop(payloadHex string, channelId uint64) (string, error) { + payload, err := hex.DecodeString(payloadHex) + if err != nil { + log.Fatalf("failed to decode types %v", err) + } + bufReader := bytes.NewBuffer(payload) + var b [8]byte + varInt, err := sphinx.ReadVarInt(bufReader, &b) + if err != nil { + return "", fmt.Errorf("failed to read payload length %v: %v", payloadHex, err) + } + + innerPayload := make([]byte, varInt) + if _, err := io.ReadFull(bufReader, innerPayload[:]); err != nil { + return "", fmt.Errorf("failed to decode payload %x: %v", innerPayload[:], err) + } + + s, _ := tlv.NewStream() + tlvMap, err := s.DecodeWithParsedTypes(bytes.NewReader(innerPayload)) + if err != nil { + return "", fmt.Errorf("DecodeWithParsedTypes failed for %x: %v", innerPayload[:], err) + } + + tt := record.NewNextHopIDRecord(&channelId) + buf := bytes.NewBuffer([]byte{}) + if err := tt.Encode(buf); err != nil { + return "", fmt.Errorf("failed to encode nexthop %x: %v", innerPayload[:], err) + } + + uTlvMap := make(map[uint64][]byte) + for t, b := range tlvMap { + if t == record.NextHopOnionType { + uTlvMap[uint64(t)] = buf.Bytes() + continue + } + uTlvMap[uint64(t)] = b + } + tlvRecords := tlv.MapToRecords(uTlvMap) + s, err = tlv.NewStream(tlvRecords...) + if err != nil { + return "", fmt.Errorf("tlv.NewStream(%x) error: %v", tlvRecords, err) + } + var newPayloadBuf bytes.Buffer + err = s.Encode(&newPayloadBuf) + if err != nil { + return "", fmt.Errorf("encode error: %v", err) + } + return hex.EncodeToString(newPayloadBuf.Bytes()), nil +} diff --git a/go.mod b/go.mod index 9860553..6566285 100644 --- a/go.mod +++ b/go.mod @@ -13,8 +13,9 @@ require ( github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 github.com/jackc/pgtype v1.8.1 github.com/jackc/pgx/v4 v4.13.0 - github.com/lightningnetwork/lightning-onion v1.0.2-0.20220211021909-bb84a1ccb0c5 + github.com/lightningnetwork/lightning-onion v1.2.0 github.com/lightningnetwork/lnd v0.15.1-beta + github.com/niftynei/glightning v0.8.2 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c google.golang.org/grpc v1.38.0 ) @@ -136,8 +137,9 @@ require ( go.uber.org/multierr v1.6.0 // indirect go.uber.org/zap v1.17.0 // indirect golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 // indirect + golang.org/x/exp v0.0.0-20221114191408-850992195362 golang.org/x/net v0.0.0-20211216030914-fe4d6282115f // indirect - golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect + golang.org/x/sys v0.1.0 // indirect golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 // indirect golang.org/x/text v0.3.7 // indirect golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba // indirect @@ -154,3 +156,5 @@ require ( ) replace github.com/lightningnetwork/lnd v0.15.1-beta => github.com/breez/lnd v0.15.0-beta.rc6.0.20220831104847-00b86a81e57a + +replace github.com/niftynei/glightning v0.8.2 => github.com/breez/glightning v0.0.0-20220822151439-7bb360481467 diff --git a/short_channel_id.go b/short_channel_id.go index c8207a2..ef87f7b 100644 --- a/short_channel_id.go +++ b/short_channel_id.go @@ -4,37 +4,40 @@ import ( "fmt" "strconv" "strings" + + "github.com/lightningnetwork/lnd/lnwire" ) type ShortChannelID uint64 func NewShortChannelIDFromString(channelID string) (*ShortChannelID, error) { - parts := strings.Split(channelID, "x") - if len(parts) != 3 { - return nil, fmt.Errorf("expected 3 parts, got %d", len(parts)) + if channelID == "" { + return nil, nil } - blockHeight, err := strconv.Atoi(parts[0]) - if err != nil { - return nil, err + fields := strings.Split(channelID, "x") + if len(fields) != 3 { + return nil, fmt.Errorf("invalid short channel id %v", channelID) } - - txIndex, err := strconv.Atoi(parts[1]) - if err != nil { - return nil, err + var blockHeight, txIndex, txPos int64 + var err error + if blockHeight, err = strconv.ParseInt(fields[0], 10, 64); err != nil { + return nil, fmt.Errorf("failed to parse block height %v", fields[0]) } - - outputIndex, err := strconv.Atoi(parts[2]) - if err != nil { - return nil, err + if txIndex, err = strconv.ParseInt(fields[1], 10, 64); err != nil { + return nil, fmt.Errorf("failed to parse block height %v", fields[1]) + } + if txPos, err = strconv.ParseInt(fields[2], 10, 64); err != nil { + return nil, fmt.Errorf("failed to parse block height %v", fields[2]) } result := ShortChannelID( - (uint64(outputIndex) & 0xFFFF) + - ((uint64(txIndex) << 16) & 0xFFFFFF0000) + - ((uint64(blockHeight) << 40) & 0xFFFFFF0000000000), + lnwire.ShortChannelID{ + BlockHeight: uint32(blockHeight), + TxIndex: uint32(txIndex), + TxPosition: uint16(txPos), + }.ToUint64(), ) - return &result, nil } From c57795643b11f0f2a701174c5318088907a15ae7 Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Fri, 18 Nov 2022 16:41:30 +0100 Subject: [PATCH 090/214] add cln to main --- main.go | 29 ++++++++++++++++++++++++----- server.go | 2 +- 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/main.go b/main.go index 1a89553..4a2160e 100644 --- a/main.go +++ b/main.go @@ -24,8 +24,30 @@ func main() { log.Fatalf("pgConnect() error: %v", err) } - client = NewLndClient() - interceptor := NewLndHtlcInterceptor(client) + runCln := os.Getenv("RUN_CLN") == "true" + runLnd := os.Getenv("RUN_LND") == "true" + + if runCln && runLnd { + log.Fatalf("One of RUN_CLN or RUN_LND must be true, not both.") + } + + if !runCln && !runLnd { + log.Fatalf("Either RUN_CLN or RUN_LND must be true.") + } + + var interceptor HtlcInterceptor + if runCln { + c := NewClnClient("lightningrpc", ".") + client = c + interceptor = NewClnHtlcInterceptor(c) + } + + if runLnd { + c := NewLndClient() + client = c + interceptor = NewLndHtlcInterceptor(c) + } + s := NewGrpcServer() info, err := client.GetInfo() @@ -39,9 +61,6 @@ func main() { nodePubkey = info.Pubkey } - go forwardingHistorySynchronize(client) - go channelsSynchronize(client) - var wg sync.WaitGroup wg.Add(2) diff --git a/server.go b/server.go index 9d2de8e..1d1ad8c 100644 --- a/server.go +++ b/server.go @@ -46,7 +46,7 @@ type server struct { } var ( - client *LndClient + client LightningClient openChannelReqGroup singleflight.Group privateKey *btcec.PrivateKey publicKey *btcec.PublicKey From 68d7741d3a76fd359a07045fa5712ac9165922e7 Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Mon, 21 Nov 2022 14:23:33 +0100 Subject: [PATCH 091/214] create client in interceptor startup --- cln_interceptor.go | 35 ++++++++++++++++++++++++++++------- htlc_interceptor.go | 1 + lnd_interceptor.go | 18 +++++++++++++++--- main.go | 31 ++++++++++++++----------------- 4 files changed, 58 insertions(+), 27 deletions(-) diff --git a/cln_interceptor.go b/cln_interceptor.go index 6414d56..a64e727 100644 --- a/cln_interceptor.go +++ b/cln_interceptor.go @@ -7,6 +7,7 @@ import ( "io" "log" "os" + "sync" "time" sphinx "github.com/lightningnetwork/lightning-onion" @@ -18,17 +19,19 @@ import ( type ClnHtlcInterceptor struct { client *ClnClient plugin *glightning.Plugin + initWg sync.WaitGroup } -func NewClnHtlcInterceptor(client *ClnClient) *ClnHtlcInterceptor { - return &ClnHtlcInterceptor{ - client: client, - } +func NewClnHtlcInterceptor() *ClnHtlcInterceptor { + i := &ClnHtlcInterceptor{} + + i.initWg.Add(1) + return i } func (i *ClnHtlcInterceptor) Start() error { //c-lightning plugin initiate - plugin := glightning.NewPlugin(onInit) + plugin := glightning.NewPlugin(i.onInit) i.plugin = plugin plugin.RegisterHooks(&glightning.Hooks{ HtlcAccepted: i.OnHtlcAccepted, @@ -52,8 +55,25 @@ func (i *ClnHtlcInterceptor) Stop() error { return nil } -func onInit(plugin *glightning.Plugin, options map[string]glightning.Option, config *glightning.Config) { +func (i *ClnHtlcInterceptor) WaitStarted() LightningClient { + i.initWg.Wait() + return i.client +} + +func (i *ClnHtlcInterceptor) onInit(plugin *glightning.Plugin, options map[string]glightning.Option, config *glightning.Config) { log.Printf("successfully init'd! %v\n", config.RpcFile) + + //lightning server + clientcln := glightning.NewLightning() + clientcln.SetTimeout(60) + clientcln.StartUp(config.RpcFile, config.LightningDir) + + i.client = &ClnClient{ + client: clientcln, + } + + log.Printf("successfull clientcln.StartUp") + i.initWg.Done() } func (i *ClnHtlcInterceptor) OnHtlcAccepted(event *glightning.HtlcAcceptedEvent) (*glightning.HtlcAcceptedResponse, error) { @@ -143,7 +163,8 @@ func (i *ClnHtlcInterceptor) resumeOrCancel(event *glightning.HtlcAcceptedEvent, func encodePayloadWithNextHop(payloadHex string, channelId uint64) (string, error) { payload, err := hex.DecodeString(payloadHex) if err != nil { - log.Fatalf("failed to decode types %v", err) + log.Printf("failed to decode types. error: %v", err) + return "", err } bufReader := bytes.NewBuffer(payload) var b [8]byte diff --git a/htlc_interceptor.go b/htlc_interceptor.go index bb487df..356463b 100644 --- a/htlc_interceptor.go +++ b/htlc_interceptor.go @@ -3,4 +3,5 @@ package main type HtlcInterceptor interface { Start() error Stop() error + WaitStarted() LightningClient } diff --git a/lnd_interceptor.go b/lnd_interceptor.go index 2683f8b..b7481e7 100644 --- a/lnd_interceptor.go +++ b/lnd_interceptor.go @@ -5,6 +5,7 @@ import ( "fmt" "log" "os" + "sync" "time" "github.com/lightningnetwork/lnd/lnrpc" @@ -17,17 +18,23 @@ import ( type LndHtlcInterceptor struct { client *LndClient stopRequested bool + initWg sync.WaitGroup } -func NewLndHtlcInterceptor(client *LndClient) *LndHtlcInterceptor { - return &LndHtlcInterceptor{ - client: client, +func NewLndHtlcInterceptor() *LndHtlcInterceptor { + i := &LndHtlcInterceptor{ + client: NewLndClient(), } + + i.initWg.Add(1) + + return i } func (i *LndHtlcInterceptor) Start() error { go forwardingHistorySynchronize(i.client) go channelsSynchronize(i.client) + i.initWg.Done() return i.intercept() } @@ -36,6 +43,11 @@ func (i *LndHtlcInterceptor) Stop() error { return nil } +func (i *LndHtlcInterceptor) WaitStarted() LightningClient { + i.initWg.Wait() + return i.client +} + func (i *LndHtlcInterceptor) intercept() error { for { if i.stopRequested { diff --git a/main.go b/main.go index 4a2160e..a181e51 100644 --- a/main.go +++ b/main.go @@ -37,30 +37,15 @@ func main() { var interceptor HtlcInterceptor if runCln { - c := NewClnClient("lightningrpc", ".") - client = c - interceptor = NewClnHtlcInterceptor(c) + interceptor = NewClnHtlcInterceptor() } if runLnd { - c := NewLndClient() - client = c - interceptor = NewLndHtlcInterceptor(c) + interceptor = NewLndHtlcInterceptor() } s := NewGrpcServer() - info, err := client.GetInfo() - if err != nil { - log.Fatalf("client.GetInfo() error: %v", err) - } - if nodeName == "" { - nodeName = info.Alias - } - if nodePubkey == "" { - nodePubkey = info.Pubkey - } - var wg sync.WaitGroup wg.Add(2) @@ -75,6 +60,18 @@ func main() { wg.Done() }() + client = interceptor.WaitStarted() + info, err := client.GetInfo() + if err != nil { + log.Fatalf("client.GetInfo() error: %v", err) + } + if nodeName == "" { + nodeName = info.Alias + } + if nodePubkey == "" { + nodePubkey = info.Pubkey + } + go func() { err := s.Start() if err == nil { From daa70c5f0e13e21fd0b22a874180ae418be1852d Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Mon, 21 Nov 2022 14:24:07 +0100 Subject: [PATCH 092/214] hex encode instead of channelpoint string --- cln_client.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cln_client.go b/cln_client.go index 610c0d5..64d88f7 100644 --- a/cln_client.go +++ b/cln_client.go @@ -112,7 +112,7 @@ func (c *ClnClient) OpenChannel(req *OpenChannelRequest) (*wire.OutPoint, error) func (c *ClnClient) GetChannel(peerID []byte, channelPoint wire.OutPoint) (*GetChannelResult, error) { pubkey := hex.EncodeToString(peerID) peer, err := c.client.GetPeer(pubkey) - fundingTxID := channelPoint.Hash.String() + fundingTxID := hex.EncodeToString(channelPoint.Hash[:]) if err != nil { log.Printf("CLN: client.GetPeer(%s) error: %v", pubkey, err) return nil, err @@ -138,7 +138,7 @@ func (c *ClnClient) GetChannel(peerID []byte, channelPoint wire.OutPoint) (*GetC } } - log.Printf("No channel found: getChannel(%v)", pubkey) + log.Printf("No channel found: getChannel(%v, %v)", pubkey, fundingTxID) return nil, fmt.Errorf("no channel found") } From ceb3ddb1ee09bec86d66ae579c9abfb1bf157a37 Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Mon, 21 Nov 2022 14:24:29 +0100 Subject: [PATCH 093/214] lock on payment hash --- intercept.go | 218 +++++++++++++++++++++++++++------------------------ 1 file changed, 114 insertions(+), 104 deletions(-) diff --git a/intercept.go b/intercept.go index dd5b98f..9978ada 100644 --- a/intercept.go +++ b/intercept.go @@ -2,6 +2,7 @@ package main import ( "bytes" + "encoding/hex" "fmt" "log" "math/big" @@ -12,6 +13,7 @@ import ( "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/record" "github.com/lightningnetwork/lnd/routing/route" + "golang.org/x/sync/singleflight" ) type interceptAction int @@ -30,6 +32,8 @@ var ( FAILURE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS interceptFailureCode = 0x4015 ) +var payHashGroup singleflight.Group + type interceptResult struct { action interceptAction failureCode interceptFailureCode @@ -40,122 +44,128 @@ type interceptResult struct { } func intercept(reqPaymentHash []byte, reqOutgoingAmountMsat uint64, reqOutgoingExpiry uint32) interceptResult { - paymentHash, paymentSecret, destination, incomingAmountMsat, outgoingAmountMsat, channelPoint, err := paymentInfo(reqPaymentHash) - if err != nil { - log.Printf("paymentInfo(%x) error: %v", reqPaymentHash, err) - return interceptResult{ - action: INTERCEPT_FAIL_HTLC, + reqPaymentHashStr := hex.EncodeToString(reqPaymentHash) + resp, _, _ := payHashGroup.Do(reqPaymentHashStr, func() (interface{}, error) { + paymentHash, paymentSecret, destination, incomingAmountMsat, outgoingAmountMsat, channelPoint, err := paymentInfo(reqPaymentHash) + if err != nil { + log.Printf("paymentInfo(%x) error: %v", reqPaymentHash, err) + return interceptResult{ + action: INTERCEPT_FAIL_HTLC, + }, nil } - } - log.Printf("paymentHash:%x\npaymentSecret:%x\ndestination:%x\nincomingAmountMsat:%v\noutgoingAmountMsat:%v\n\n", - paymentHash, paymentSecret, destination, incomingAmountMsat, outgoingAmountMsat) - if paymentSecret != nil { + log.Printf("paymentHash:%x\npaymentSecret:%x\ndestination:%x\nincomingAmountMsat:%v\noutgoingAmountMsat:%v\n\n", + paymentHash, paymentSecret, destination, incomingAmountMsat, outgoingAmountMsat) + if paymentSecret != nil { - if channelPoint == nil { - if bytes.Equal(paymentHash, reqPaymentHash) { - channelPoint, err = openChannel(client, reqPaymentHash, destination, incomingAmountMsat) - log.Printf("openChannel(%x, %v) err: %v", destination, incomingAmountMsat, err) - if err != nil { - return interceptResult{ - action: INTERCEPT_FAIL_HTLC, + if channelPoint == nil { + if bytes.Equal(paymentHash, reqPaymentHash) { + channelPoint, err = openChannel(client, reqPaymentHash, destination, incomingAmountMsat) + log.Printf("openChannel(%x, %v) err: %v", destination, incomingAmountMsat, err) + if err != nil { + return interceptResult{ + action: INTERCEPT_FAIL_HTLC, + }, nil + } + } else { //probing + failureCode := FAILURE_TEMPORARY_CHANNEL_FAILURE + isConnected, _ := client.IsConnected(destination) + if err != nil || !*isConnected { + failureCode = FAILURE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS } - } - } else { //probing - failureCode := FAILURE_TEMPORARY_CHANNEL_FAILURE - isConnected, _ := client.IsConnected(destination) - if err != nil || !*isConnected { - failureCode = FAILURE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS - } + return interceptResult{ + action: INTERCEPT_FAIL_HTLC_WITH_CODE, + failureCode: failureCode, + }, nil + } + } + + pubKey, err := btcec.ParsePubKey(destination) + if err != nil { + log.Printf("btcec.ParsePubKey(%x): %v", destination, err) return interceptResult{ - action: INTERCEPT_FAIL_HTLC_WITH_CODE, - failureCode: failureCode, - } + action: INTERCEPT_FAIL_HTLC, + }, nil + } + + sessionKey, err := btcec.NewPrivateKey() + if err != nil { + log.Printf("btcec.NewPrivateKey(): %v", err) + return interceptResult{ + action: INTERCEPT_FAIL_HTLC, + }, nil + } + + var bigProd, bigAmt big.Int + amt := (bigAmt.Div(bigProd.Mul(big.NewInt(outgoingAmountMsat), big.NewInt(int64(reqOutgoingAmountMsat))), big.NewInt(incomingAmountMsat))).Int64() + + var addr [32]byte + copy(addr[:], paymentSecret) + hop := route.Hop{ + AmtToForward: lnwire.MilliSatoshi(amt), + OutgoingTimeLock: reqOutgoingExpiry, + MPP: record.NewMPP(lnwire.MilliSatoshi(outgoingAmountMsat), addr), + CustomRecords: make(record.CustomSet), + } + + var b bytes.Buffer + err = hop.PackHopPayload(&b, uint64(0)) + if err != nil { + log.Printf("hop.PackHopPayload(): %v", err) + return interceptResult{ + action: INTERCEPT_FAIL_HTLC, + }, nil + } + + payload, err := sphinx.NewHopPayload(nil, b.Bytes()) + if err != nil { + log.Printf("sphinx.NewHopPayload(): %v", err) + return interceptResult{ + action: INTERCEPT_FAIL_HTLC, + }, nil + } + + var sphinxPath sphinx.PaymentPath + sphinxPath[0] = sphinx.OnionHop{ + NodePub: *pubKey, + HopPayload: payload, + } + sphinxPacket, err := sphinx.NewOnionPacket( + &sphinxPath, sessionKey, reqPaymentHash, + sphinx.DeterministicPacketFiller, + ) + if err != nil { + log.Printf("sphinx.NewOnionPacket(): %v", err) + return interceptResult{ + action: INTERCEPT_FAIL_HTLC, + }, nil + } + var onionBlob bytes.Buffer + err = sphinxPacket.Encode(&onionBlob) + if err != nil { + log.Printf("sphinxPacket.Encode(): %v", err) + return interceptResult{ + action: INTERCEPT_FAIL_HTLC, + }, nil } - } - pubKey, err := btcec.ParsePubKey(destination) - if err != nil { - log.Printf("btcec.ParsePubKey(%x): %v", destination, err) return interceptResult{ - action: INTERCEPT_FAIL_HTLC, - } - } - - sessionKey, err := btcec.NewPrivateKey() - if err != nil { - log.Printf("btcec.NewPrivateKey(): %v", err) + action: INTERCEPT_RESUME_OR_CANCEL, + destination: destination, + channelPoint: channelPoint, + amountMsat: uint64(amt), + onionBlob: onionBlob.Bytes(), + }, nil + } else { return interceptResult{ - action: INTERCEPT_FAIL_HTLC, - } + action: INTERCEPT_RESUME, + }, nil } + }) - var bigProd, bigAmt big.Int - amt := (bigAmt.Div(bigProd.Mul(big.NewInt(outgoingAmountMsat), big.NewInt(int64(reqOutgoingAmountMsat))), big.NewInt(incomingAmountMsat))).Int64() - - var addr [32]byte - copy(addr[:], paymentSecret) - hop := route.Hop{ - AmtToForward: lnwire.MilliSatoshi(amt), - OutgoingTimeLock: reqOutgoingExpiry, - MPP: record.NewMPP(lnwire.MilliSatoshi(outgoingAmountMsat), addr), - CustomRecords: make(record.CustomSet), - } - - var b bytes.Buffer - err = hop.PackHopPayload(&b, uint64(0)) - if err != nil { - log.Printf("hop.PackHopPayload(): %v", err) - return interceptResult{ - action: INTERCEPT_FAIL_HTLC, - } - } - - payload, err := sphinx.NewHopPayload(nil, b.Bytes()) - if err != nil { - log.Printf("sphinx.NewHopPayload(): %v", err) - return interceptResult{ - action: INTERCEPT_FAIL_HTLC, - } - } - - var sphinxPath sphinx.PaymentPath - sphinxPath[0] = sphinx.OnionHop{ - NodePub: *pubKey, - HopPayload: payload, - } - sphinxPacket, err := sphinx.NewOnionPacket( - &sphinxPath, sessionKey, reqPaymentHash, - sphinx.DeterministicPacketFiller, - ) - if err != nil { - log.Printf("sphinx.NewOnionPacket(): %v", err) - return interceptResult{ - action: INTERCEPT_FAIL_HTLC, - } - } - var onionBlob bytes.Buffer - err = sphinxPacket.Encode(&onionBlob) - if err != nil { - log.Printf("sphinxPacket.Encode(): %v", err) - return interceptResult{ - action: INTERCEPT_FAIL_HTLC, - } - } - - return interceptResult{ - action: INTERCEPT_RESUME_OR_CANCEL, - destination: destination, - channelPoint: channelPoint, - amountMsat: uint64(amt), - onionBlob: onionBlob.Bytes(), - } - } else { - return interceptResult{ - action: INTERCEPT_RESUME, - } - } + return resp.(interceptResult) } + func checkPayment(incomingAmountMsat, outgoingAmountMsat int64) error { fees := incomingAmountMsat * channelFeePermyriad / 10_000 / 1_000 * 1_000 if fees < channelMinimumFeeMsat { From 4cc9fcbc1c58b099a8717dff7cb0eecbda5f17f8 Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Mon, 21 Nov 2022 14:24:53 +0100 Subject: [PATCH 094/214] return 0 shortchannelid by default --- short_channel_id.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/short_channel_id.go b/short_channel_id.go index ef87f7b..8ce0b94 100644 --- a/short_channel_id.go +++ b/short_channel_id.go @@ -12,7 +12,8 @@ type ShortChannelID uint64 func NewShortChannelIDFromString(channelID string) (*ShortChannelID, error) { if channelID == "" { - return nil, nil + c := ShortChannelID(0) + return &c, nil } fields := strings.Split(channelID, "x") From d186e06323776df881125bfc7419ae6b10a451a3 Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Mon, 21 Nov 2022 14:25:06 +0100 Subject: [PATCH 095/214] add itests --- go.mod | 43 +++++-- itest/intercept_zero_conf_test.go | 141 +++++++++++++++++++++ itest/lspd_node.go | 200 ++++++++++++++++++++++++++++++ itest/postgres.go | 192 ++++++++++++++++++++++++++++ itest/test_common.go | 14 +++ itest/zero_conf_node.go | 175 ++++++++++++++++++++++++++ 6 files changed, 753 insertions(+), 12 deletions(-) create mode 100644 itest/intercept_zero_conf_test.go create mode 100644 itest/lspd_node.go create mode 100644 itest/postgres.go create mode 100644 itest/test_common.go create mode 100644 itest/zero_conf_node.go diff --git a/go.mod b/go.mod index 6566285..173a796 100644 --- a/go.mod +++ b/go.mod @@ -4,11 +4,14 @@ go 1.19 require ( github.com/aws/aws-sdk-go v1.30.20 + github.com/breez/lntest v0.0.3 github.com/btcsuite/btcd v0.23.1 github.com/btcsuite/btcd/btcec/v2 v2.2.1 github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 github.com/caddyserver/certmagic v0.11.2 github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 + github.com/docker/docker v20.10.21+incompatible + github.com/docker/go-connections v0.4.0 github.com/golang/protobuf v1.5.2 github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 github.com/jackc/pgtype v1.8.1 @@ -17,7 +20,23 @@ require ( github.com/lightningnetwork/lnd v0.15.1-beta github.com/niftynei/glightning v0.8.2 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c - google.golang.org/grpc v1.38.0 + google.golang.org/grpc v1.50.1 + gotest.tools v2.2.0+incompatible +) + +require ( + github.com/Microsoft/go-winio v0.6.0 // indirect + github.com/docker/distribution v2.8.1+incompatible // indirect + github.com/docker/go-units v0.5.0 // indirect + github.com/google/go-cmp v0.5.8 // indirect + github.com/moby/term v0.0.0-20221120202655-abb19827d345 // indirect + github.com/morikuni/aec v1.0.0 // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/opencontainers/image-spec v1.0.2 // indirect + github.com/pkg/errors v0.9.1 // indirect + golang.org/x/mod v0.6.0 // indirect + golang.org/x/tools v0.2.0 // indirect + gotest.tools/v3 v3.4.0 // indirect ) require ( @@ -88,7 +107,7 @@ require ( github.com/lightningnetwork/lnd/kvdb v1.3.1 // indirect github.com/lightningnetwork/lnd/queue v1.1.0 // indirect github.com/lightningnetwork/lnd/ticker v1.1.0 // indirect - github.com/lightningnetwork/lnd/tlv v1.0.3 // indirect + github.com/lightningnetwork/lnd/tlv v1.0.3 github.com/lightningnetwork/lnd/tor v1.0.1 // indirect github.com/ltcsuite/ltcd v0.0.0-20190101042124-f37f8bf35796 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect @@ -108,8 +127,8 @@ require ( github.com/sirupsen/logrus v1.7.0 // indirect github.com/soheilhy/cmux v0.1.5 // indirect github.com/spf13/pflag v1.0.5 // indirect - github.com/stretchr/objx v0.2.0 // indirect - github.com/stretchr/testify v1.7.1 // indirect + github.com/stretchr/objx v0.5.0 // indirect + github.com/stretchr/testify v1.8.1 // indirect github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802 // indirect github.com/ulikunitz/xz v0.5.10 // indirect @@ -134,27 +153,27 @@ require ( go.opentelemetry.io/otel/trace v0.20.0 // indirect go.opentelemetry.io/proto/otlp v0.7.0 // indirect go.uber.org/atomic v1.7.0 // indirect - go.uber.org/multierr v1.6.0 // indirect + go.uber.org/multierr v1.8.0 // indirect go.uber.org/zap v1.17.0 // indirect - golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 // indirect + golang.org/x/crypto v0.1.0 // indirect golang.org/x/exp v0.0.0-20221114191408-850992195362 - golang.org/x/net v0.0.0-20211216030914-fe4d6282115f // indirect + golang.org/x/net v0.1.0 // indirect golang.org/x/sys v0.1.0 // indirect - golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 // indirect - golang.org/x/text v0.3.7 // indirect + golang.org/x/term v0.1.0 // indirect + golang.org/x/text v0.4.0 // indirect golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba // indirect google.golang.org/genproto v0.0.0-20210617175327-b9e0b3197ced // indirect - google.golang.org/protobuf v1.26.0 // indirect + google.golang.org/protobuf v1.27.1 // indirect gopkg.in/errgo.v1 v1.0.1 // indirect gopkg.in/macaroon-bakery.v2 v2.0.1 // indirect gopkg.in/macaroon.v2 v2.0.0 // indirect gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect gopkg.in/square/go-jose.v2 v2.3.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect - gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect sigs.k8s.io/yaml v1.2.0 // indirect ) replace github.com/lightningnetwork/lnd v0.15.1-beta => github.com/breez/lnd v0.15.0-beta.rc6.0.20220831104847-00b86a81e57a -replace github.com/niftynei/glightning v0.8.2 => github.com/breez/glightning v0.0.0-20220822151439-7bb360481467 +replace github.com/niftynei/glightning v0.8.2 => github.com/breez/glightning v0.0.0-20221124075140-383be4672b47 diff --git a/itest/intercept_zero_conf_test.go b/itest/intercept_zero_conf_test.go new file mode 100644 index 0000000..2318e56 --- /dev/null +++ b/itest/intercept_zero_conf_test.go @@ -0,0 +1,141 @@ +package itest + +import ( + "log" + "testing" + "time" + + "github.com/breez/lntest" + lspd "github.com/breez/lspd/rpc" + "gotest.tools/assert" +) + +func TestOpenZeroConfChannelOnReceive(t *testing.T) { + harness := lntest.NewTestHarness(t) + defer harness.TearDown() + + timeout := time.Now().Add(time.Minute) + + miner := lntest.NewMiner(harness) + alice := lntest.NewCoreLightningNode(harness, miner, "Alice", timeout) + bob := NewZeroConfNode(harness, miner, "Bob", timeout) + lsp := NewLspdNode(harness, miner, "Lsp", timeout) + + alice.Fund(10000000, timeout) + lsp.lightningNode.Fund(10000000, timeout) + + log.Print("Opening channel between Alice and the lsp") + alice.OpenChannelAndWait(lsp.lightningNode, &lntest.OpenChannelOptions{ + AmountSat: 1000000, + }, timeout) + + log.Printf("Adding bob's invoices") + outerAmountMsat := uint64(2100000) + innerAmountMsat := uint64(2100000) + description := "Please pay me" + innerInvoice, outerInvoice := bob.GenerateInvoices(generateInvoicesRequest{ + innerAmountMsat: innerAmountMsat, + outerAmountMsat: outerAmountMsat, + description: description, + lsp: lsp, + }) + + log.Print("Connecting bob to lspd") + bob.lightningNode.ConnectPeer(lsp.lightningNode) + + // NOTE: We pretend to be paying fees to the lsp, but actually we won't. + log.Printf("Registering payment with lsp") + pretendAmount := outerAmountMsat - 2000000 + lsp.RegisterPayment(&lspd.PaymentInformation{ + PaymentHash: innerInvoice.paymentHash, + PaymentSecret: innerInvoice.paymentSecret, + Destination: bob.lightningNode.NodeId(), + IncomingAmountMsat: int64(outerAmountMsat), + OutgoingAmountMsat: int64(pretendAmount), + }) + + log.Printf("Alice paying") + payResp := alice.Pay(outerInvoice.bolt11, timeout) + bobInvoice := bob.lightningNode.GetInvoice(payResp.PaymentHash) + + assert.DeepEqual(t, payResp.PaymentPreimage, bobInvoice.PaymentPreimage) + assert.Equal(t, outerAmountMsat, bobInvoice.AmountReceivedMsat) +} + +func TestOpenZeroConfSingleHtlc(t *testing.T) { + harness := lntest.NewTestHarness(t) + defer harness.TearDown() + + timeout := time.Now().Add(time.Minute) + + miner := lntest.NewMiner(harness) + alice := lntest.NewCoreLightningNode(harness, miner, "Alice", timeout) + bob := NewZeroConfNode(harness, miner, "Bob", timeout) + lsp := NewLspdNode(harness, miner, "Lsp", timeout) + + alice.Fund(10000000, timeout) + lsp.lightningNode.Fund(10000000, timeout) + + log.Print("Opening channel between Alice and the lsp") + channel := alice.OpenChannelAndWait(lsp.lightningNode, &lntest.OpenChannelOptions{ + AmountSat: 1000000, + }, timeout) + + log.Printf("Adding bob's invoices") + outerAmountMsat := uint64(2100000) + innerAmountMsat := uint64(2100000) + description := "Please pay me" + innerInvoice, outerInvoice := bob.GenerateInvoices(generateInvoicesRequest{ + innerAmountMsat: innerAmountMsat, + outerAmountMsat: outerAmountMsat, + description: description, + lsp: lsp, + }) + + log.Print("Connecting bob to lspd") + bob.lightningNode.ConnectPeer(lsp.lightningNode) + + // NOTE: We pretend to be paying fees to the lsp, but actually we won't. + log.Printf("Registering payment with lsp") + pretendAmount := outerAmountMsat - 2000000 + lsp.RegisterPayment(&lspd.PaymentInformation{ + PaymentHash: innerInvoice.paymentHash, + PaymentSecret: innerInvoice.paymentSecret, + Destination: bob.lightningNode.NodeId(), + IncomingAmountMsat: int64(outerAmountMsat), + OutgoingAmountMsat: int64(pretendAmount), + }) + + log.Printf("Alice paying") + route := constructRoute(lsp.lightningNode, bob.lightningNode, channel.ChannelId, "1x0x0", outerAmountMsat) + alice.StartPayPartViaRoute(outerAmountMsat, outerInvoice.paymentHash, outerInvoice.paymentSecret, 0, route) + payResp := alice.WaitForPaymentPart(outerInvoice.paymentHash, timeout, 0) + bobInvoice := bob.lightningNode.GetInvoice(payResp.PaymentHash) + + assert.DeepEqual(t, payResp.PaymentPreimage, bobInvoice.PaymentPreimage) + assert.Equal(t, outerAmountMsat, bobInvoice.AmountReceivedMsat) +} + +func constructRoute( + lsp *lntest.CoreLightningNode, + bob *lntest.CoreLightningNode, + aliceLspChannel string, + lspBobChannel string, + amountMsat uint64) *lntest.Route { + return &lntest.Route{ + Route: []*lntest.Hop{ + { + Id: lsp.NodeId(), + Channel: aliceLspChannel, + AmountMsat: amountMsat + uint64(lspBaseFeeMsat) + (amountMsat * uint64(lspFeeRatePpm) / 1000000), + Delay: 144 + lspCltvDelta, + }, + { + Id: bob.NodeId(), + Channel: lspBobChannel, + AmountMsat: amountMsat, + Delay: 144, + }, + }, + } +} diff --git a/itest/lspd_node.go b/itest/lspd_node.go new file mode 100644 index 0000000..dc76539 --- /dev/null +++ b/itest/lspd_node.go @@ -0,0 +1,200 @@ +package itest + +import ( + "bufio" + context "context" + "flag" + "fmt" + "log" + "os" + "os/exec" + "path/filepath" + "time" + + "github.com/breez/lntest" + "github.com/breez/lspd/btceclegacy" + lspd "github.com/breez/lspd/rpc" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/golang/protobuf/proto" + grpc "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" +) + +var ( + lspdExecutable = flag.String( + "lspdexec", "", "full path to lpsd plugin binary", + ) + lspdMigrationsDir = flag.String( + "lspdmigrationsdir", "", "full path to lspd sql migrations directory", + ) +) + +var ( + lspBaseFeeMsat uint32 = 1000 + lspFeeRatePpm uint32 = 1 + lspCltvDelta uint16 = 40 +) + +type LspNode struct { + harness *lntest.TestHarness + lightningNode *lntest.CoreLightningNode + rpc lspd.ChannelOpenerClient + rpcPort uint32 + rpcHost string + privateKey btcec.PrivateKey + publicKey btcec.PublicKey + postgresBackend *PostgresContainer + scriptDir string +} + +func NewLspdNode(h *lntest.TestHarness, m *lntest.Miner, name string, timeout time.Time) *LspNode { + scriptDir := h.GetDirectory(fmt.Sprintf("lspd-%s", name)) + migrationsDir, err := GetMigrationsDir() + lntest.CheckError(h.T, err) + + pgLogfile := filepath.Join(scriptDir, "postgres.log") + postgresBackend := StartPostgresContainer(h.T, h.Ctx, pgLogfile) + postgresBackend.RunMigrations(h.T, h.Ctx, migrationsDir) + + lspdBinary, err := GetLspdBinary() + lntest.CheckError(h.T, err) + + lspdPort, err := lntest.GetPort() + lntest.CheckError(h.T, err) + + lspdPrivateKeyBytes, err := GenerateRandomBytes(32) + lntest.CheckError(h.T, err) + + priv, publ := btcec.PrivKeyFromBytes(lspdPrivateKeyBytes) + + host := "localhost" + grpcAddress := fmt.Sprintf("%s:%d", host, lspdPort) + env := []string{ + "NODE_NAME=lsp", + "NODE_PUBKEY=dunno", + "NODE_HOST=host:port", + "RUN_CLN=true", + "TOKEN=hello", + fmt.Sprintf("DATABASE_URL=%s", postgresBackend.ConnectionString()), + fmt.Sprintf("LISTEN_ADDRESS=%s", grpcAddress), + fmt.Sprintf("LSPD_PRIVATE_KEY=%x", lspdPrivateKeyBytes), + } + + scriptFilePath := filepath.Join(scriptDir, "start-lspd.sh") + log.Printf("Creating lspd startup script at %s", scriptFilePath) + scriptFile, err := os.OpenFile(scriptFilePath, os.O_CREATE|os.O_WRONLY, 0755) + lntest.CheckError(h.T, err) + + writer := bufio.NewWriter(scriptFile) + _, err = writer.WriteString("#!/bin/bash\n") + lntest.CheckError(h.T, err) + + for _, str := range env { + _, err = writer.WriteString("export " + str + "\n") + lntest.CheckError(h.T, err) + } + + _, err = writer.WriteString(lspdBinary + "\n") + lntest.CheckError(h.T, err) + + err = writer.Flush() + lntest.CheckError(h.T, err) + scriptFile.Close() + + args := []string{ + fmt.Sprintf("--plugin=%s", scriptFilePath), + fmt.Sprintf("--fee-base=%d", lspBaseFeeMsat), + fmt.Sprintf("--fee-per-satoshi=%d", lspFeeRatePpm), + fmt.Sprintf("--cltv-delta=%d", lspCltvDelta), + } + + lightningNode := lntest.NewCoreLightningNode(h, m, name, timeout, args...) + + conn, err := grpc.Dial( + grpcAddress, + grpc.WithTransportCredentials(insecure.NewCredentials()), + grpc.WithPerRPCCredentials(&token{token: "hello"}), + ) + lntest.CheckError(h.T, err) + + client := lspd.NewChannelOpenerClient(conn) + + lspNode := &LspNode{ + harness: h, + lightningNode: lightningNode, + rpc: client, + rpcPort: lspdPort, + rpcHost: host, + privateKey: *priv, + publicKey: *publ, + postgresBackend: postgresBackend, + scriptDir: scriptDir, + } + + h.AddStoppable(lspNode) + h.AddCleanable(lspNode) + h.RegisterLogfile(pgLogfile, fmt.Sprintf("%s-postgres", name)) + return lspNode +} + +func (l *LspNode) RegisterPayment(paymentInfo *lspd.PaymentInformation) { + serialized, err := proto.Marshal(paymentInfo) + lntest.CheckError(l.harness.T, err) + + encrypted, err := btceclegacy.Encrypt(&l.publicKey, serialized) + lntest.CheckError(l.harness.T, err) + + log.Printf("Registering payment") + _, err = l.rpc.RegisterPayment( + l.harness.Ctx, + &lspd.RegisterPaymentRequest{ + Blob: encrypted, + }, + ) + lntest.CheckError(l.harness.T, err) +} + +func (l *LspNode) TearDown() error { + // NOTE: The lightningnode will be torn down on its own. + return l.postgresBackend.Shutdown(l.harness.Ctx) +} + +func (l *LspNode) Cleanup() error { + return l.postgresBackend.Cleanup(l.harness.Ctx) +} + +func (l *LspNode) NodeId() []byte { + return l.lightningNode.NodeId() +} + +func GetLspdBinary() (string, error) { + if lspdExecutable != nil { + return *lspdExecutable, nil + } + + return exec.LookPath("lspd") +} + +func GetMigrationsDir() (string, error) { + if lspdMigrationsDir != nil { + return *lspdMigrationsDir, nil + } + + return exec.LookPath("lspdmigrationsdir") +} + +type token struct { + token string +} + +func (t *token) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) { + m := make(map[string]string) + m["authorization"] = "Bearer " + t.token + return m, nil +} + +// RequireTransportSecurity indicates whether the credentials requires +// transport security. +func (t *token) RequireTransportSecurity() bool { + return false +} diff --git a/itest/postgres.go b/itest/postgres.go new file mode 100644 index 0000000..91539e0 --- /dev/null +++ b/itest/postgres.go @@ -0,0 +1,192 @@ +package itest + +import ( + "context" + "encoding/binary" + "errors" + "fmt" + "io" + "log" + "os" + "path/filepath" + "sort" + "strconv" + "testing" + "time" + + "github.com/breez/lntest" + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/container" + "github.com/docker/docker/client" + "github.com/docker/go-connections/nat" + "github.com/jackc/pgx/v4/pgxpool" +) + +type PostgresContainer struct { + id string + password string + port uint32 + cli *client.Client +} + +func StartPostgresContainer(t *testing.T, ctx context.Context, logfile string) *PostgresContainer { + cli, err := client.NewClientWithOpts(client.FromEnv) + lntest.CheckError(t, err) + + image := "postgres:15" + _, _, err = cli.ImageInspectWithRaw(ctx, image) + if err != nil { + if !client.IsErrNotFound(err) { + lntest.CheckError(t, err) + } + + pullReader, err := cli.ImagePull(ctx, image, types.ImagePullOptions{}) + lntest.CheckError(t, err) + _, err = io.Copy(io.Discard, pullReader) + pullReader.Close() + lntest.CheckError(t, err) + } + + port, err := lntest.GetPort() + lntest.CheckError(t, err) + createResp, err := cli.ContainerCreate(ctx, &container.Config{ + Image: image, + Cmd: []string{ + "postgres", + "-c", + "log_statement=all", + }, + Env: []string{ + "POSTGRES_DB=postgres", + "POSTGRES_PASSWORD=pgpassword", + "POSTGRES_USER=postgres", + }, + Healthcheck: &container.HealthConfig{ + Test: []string{"CMD-SHELL", "pg_isready -U postgres"}, + Interval: time.Second, + Timeout: time.Second, + Retries: 10, + }, + }, &container.HostConfig{ + PortBindings: nat.PortMap{ + "5432/tcp": []nat.PortBinding{ + {HostPort: strconv.FormatUint(uint64(port), 10)}, + }, + }, + }, + nil, + nil, + "", + ) + lntest.CheckError(t, err) + + err = cli.ContainerStart(ctx, createResp.ID, types.ContainerStartOptions{}) + lntest.CheckError(t, err) + +HealthCheck: + for { + inspect, err := cli.ContainerInspect(ctx, createResp.ID) + lntest.CheckError(t, err) + + status := inspect.State.Health.Status + switch status { + case "unhealthy": + lntest.CheckError(t, errors.New("container unhealthy")) + case "healthy": + break HealthCheck + default: + time.Sleep(500 * time.Millisecond) + } + } + + ct := &PostgresContainer{ + id: createResp.ID, + password: "pgpassword", + port: port, + cli: cli, + } + + go ct.monitorLogs(logfile) + return ct +} + +func (c *PostgresContainer) monitorLogs(logfile string) { + i, err := c.cli.ContainerLogs(context.Background(), c.id, types.ContainerLogsOptions{ + ShowStderr: true, + ShowStdout: true, + Timestamps: false, + Follow: true, + Tail: "40", + }) + if err != nil { + log.Printf("Could not get container logs: %v", err) + return + } + defer i.Close() + + file, err := os.OpenFile(logfile, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0600) + if err != nil { + log.Printf("Could not create container log file: %v", err) + return + } + defer file.Close() + + hdr := make([]byte, 8) + for { + _, err := i.Read(hdr) + if err != nil { + return + } + count := binary.BigEndian.Uint32(hdr[4:]) + dat := make([]byte, count) + _, err = i.Read(dat) + if err != nil { + return + } + _, err = file.Write(dat) + if err != nil { + return + } + } +} + +func (c *PostgresContainer) ConnectionString() string { + return fmt.Sprintf("postgres://postgres:%s@127.0.0.1:%d/postgres", c.password, c.port) +} + +func (c *PostgresContainer) Shutdown(ctx context.Context) error { + defer c.cli.Close() + timeout := time.Second + err := c.cli.ContainerStop(ctx, c.id, &timeout) + return err +} + +func (c *PostgresContainer) Cleanup(ctx context.Context) error { + cli, err := client.NewClientWithOpts(client.FromEnv) + if err != nil { + return err + } + defer cli.Close() + return cli.ContainerRemove(ctx, c.id, types.ContainerRemoveOptions{ + Force: true, + }) +} + +func (c *PostgresContainer) RunMigrations(t *testing.T, ctx context.Context, migrationDir string) { + filenames, err := filepath.Glob(filepath.Join(migrationDir, "*.up.sql")) + lntest.CheckError(t, err) + + sort.Strings(filenames) + + pgxPool, err := pgxpool.Connect(context.Background(), c.ConnectionString()) + lntest.CheckError(t, err) + defer pgxPool.Close() + + for _, filename := range filenames { + data, err := os.ReadFile(filename) + lntest.CheckError(t, err) + + _, err = pgxPool.Exec(ctx, string(data)) + lntest.CheckError(t, err) + } +} diff --git a/itest/test_common.go b/itest/test_common.go new file mode 100644 index 0000000..50600c7 --- /dev/null +++ b/itest/test_common.go @@ -0,0 +1,14 @@ +package itest + +import "crypto/rand" + +func GenerateRandomBytes(n int) ([]byte, error) { + b := make([]byte, n) + _, err := rand.Read(b) + // Note that err == nil only if we read len(b) bytes. + if err != nil { + return nil, err + } + + return b, nil +} diff --git a/itest/zero_conf_node.go b/itest/zero_conf_node.go new file mode 100644 index 0000000..ef7c1db --- /dev/null +++ b/itest/zero_conf_node.go @@ -0,0 +1,175 @@ +package itest + +import ( + "bufio" + "crypto/sha256" + "fmt" + "log" + "os" + "path/filepath" + "time" + + "github.com/breez/lntest" + btcec "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/ecdsa" + "github.com/btcsuite/btcd/chaincfg" + "github.com/decred/dcrd/dcrec/secp256k1/v4" + "github.com/lightningnetwork/lnd/lnwire" + "github.com/lightningnetwork/lnd/zpay32" +) + +type ZeroConfNode struct { + name string + harness *lntest.TestHarness + lightningNode *lntest.CoreLightningNode + privKey *secp256k1.PrivateKey + scriptDir string +} + +var pluginContent string = `#!/usr/bin/env python3 +"""Use the openchannel hook to selectively opt-into zeroconf +""" + +from pyln.client import Plugin + +plugin = Plugin() + + +@plugin.hook('openchannel') +def on_openchannel(openchannel, plugin, **kwargs): + plugin.log(repr(openchannel)) + mindepth = int(0) + + plugin.log(f"This peer is in the zeroconf allowlist, setting mindepth={mindepth}") + return {'result': 'continue', 'mindepth': mindepth} + +plugin.run() +` + +var pluginStartupContent string = `python3 -m venv %s > /dev/null 2>&1 +source %s > /dev/null 2>&1 +pip install pyln-client > /dev/null 2>&1 +python %s +` + +func NewZeroConfNode(h *lntest.TestHarness, m *lntest.Miner, name string, timeout time.Time) *ZeroConfNode { + privKey, err := btcec.NewPrivateKey() + lntest.CheckError(h.T, err) + + s := privKey.Serialize() + + scriptDir, err := os.MkdirTemp(h.Dir, name) + lntest.CheckError(h.T, err) + pythonFilePath := filepath.Join(scriptDir, "zero_conf_plugin.py") + pythonFile, err := os.OpenFile(pythonFilePath, os.O_CREATE|os.O_WRONLY, 0755) + lntest.CheckError(h.T, err) + + pythonWriter := bufio.NewWriter(pythonFile) + _, err = pythonWriter.WriteString(pluginContent) + lntest.CheckError(h.T, err) + + err = pythonWriter.Flush() + lntest.CheckError(h.T, err) + pythonFile.Close() + + pluginFilePath := filepath.Join(scriptDir, "start_zero_conf_plugin.sh") + pluginFile, err := os.OpenFile(pluginFilePath, os.O_CREATE|os.O_WRONLY, 0755) + lntest.CheckError(h.T, err) + + pluginWriter := bufio.NewWriter(pluginFile) + venvDir := filepath.Join(scriptDir, "venv") + activatePath := filepath.Join(venvDir, "bin", "activate") + _, err = pluginWriter.WriteString(fmt.Sprintf(pluginStartupContent, venvDir, activatePath, pythonFilePath)) + lntest.CheckError(h.T, err) + + err = pluginWriter.Flush() + lntest.CheckError(h.T, err) + pluginFile.Close() + + node := lntest.NewCoreLightningNode( + h, + m, + name, + timeout, + fmt.Sprintf("--dev-force-privkey=%x", s), + fmt.Sprintf("--plugin=%s", pluginFilePath), + ) + + return &ZeroConfNode{ + name: name, + harness: h, + lightningNode: node, + scriptDir: scriptDir, + privKey: privKey, + } +} + +type generateInvoicesRequest struct { + innerAmountMsat uint64 + outerAmountMsat uint64 + description string + lsp *LspNode +} + +type invoice struct { + bolt11 string + paymentHash []byte + paymentSecret []byte + paymentPreimage []byte + expiresAt uint64 +} + +func (n *ZeroConfNode) GenerateInvoices(req generateInvoicesRequest) (invoice, invoice) { + preimage, err := GenerateRandomBytes(32) + lntest.CheckError(n.harness.T, err) + + lspNodeId, err := btcec.ParsePubKey(req.lsp.lightningNode.NodeId()) + lntest.CheckError(n.harness.T, err) + + log.Printf("Adding bob's invoices") + innerInvoice := n.lightningNode.CreateBolt11Invoice(&lntest.CreateInvoiceOptions{ + AmountMsat: req.innerAmountMsat, + Description: &req.description, + Preimage: &preimage, + }) + outerInvoiceRaw, err := zpay32.Decode(innerInvoice.Bolt11, &chaincfg.RegressionNetParams) + lntest.CheckError(n.harness.T, err) + + milliSat := lnwire.MilliSatoshi(req.outerAmountMsat) + outerInvoiceRaw.MilliSat = &milliSat + fakeChanId := &lnwire.ShortChannelID{BlockHeight: 1, TxIndex: 0, TxPosition: 0} + outerInvoiceRaw.RouteHints = append(outerInvoiceRaw.RouteHints, []zpay32.HopHint{ + { + NodeID: lspNodeId, + ChannelID: fakeChanId.ToUint64(), + FeeBaseMSat: lspBaseFeeMsat, + FeeProportionalMillionths: lspFeeRatePpm, + CLTVExpiryDelta: lspCltvDelta, + }, + }) + + outerInvoice, err := outerInvoiceRaw.Encode(zpay32.MessageSigner{ + SignCompact: func(msg []byte) ([]byte, error) { + hash := sha256.Sum256(msg) + return ecdsa.SignCompact(n.privKey, hash[:], true) + }, + }) + lntest.CheckError(n.harness.T, err) + + inner := invoice{ + bolt11: innerInvoice.Bolt11, + paymentHash: innerInvoice.PaymentHash, + paymentSecret: innerInvoice.PaymentSecret, + paymentPreimage: preimage, + expiresAt: innerInvoice.ExpiresAt, + } + outer := invoice{ + bolt11: outerInvoice, + paymentHash: outerInvoiceRaw.PaymentHash[:], + paymentSecret: innerInvoice.PaymentSecret, + paymentPreimage: preimage, + expiresAt: innerInvoice.ExpiresAt, + } + + return inner, outer +} From b3c5b94531ff3e0f3e50c8e900be9917588c6d52 Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Sat, 3 Dec 2022 10:43:12 +0100 Subject: [PATCH 096/214] run combined tests for LND and CLN --- go.mod | 4 +- itest/intercept_zero_conf_test.go | 75 ++++---- itest/lspd_node.go | 272 ++++++++++++++++++++++-------- itest/lspd_test.go | 70 ++++++++ itest/postgres.go | 28 +-- itest/zero_conf_node.go | 9 +- 6 files changed, 327 insertions(+), 131 deletions(-) create mode 100644 itest/lspd_test.go diff --git a/go.mod b/go.mod index 173a796..a6b6cf9 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.19 require ( github.com/aws/aws-sdk-go v1.30.20 - github.com/breez/lntest v0.0.3 + github.com/breez/lntest v0.0.6 github.com/btcsuite/btcd v0.23.1 github.com/btcsuite/btcd/btcec/v2 v2.2.1 github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 @@ -176,4 +176,4 @@ require ( replace github.com/lightningnetwork/lnd v0.15.1-beta => github.com/breez/lnd v0.15.0-beta.rc6.0.20220831104847-00b86a81e57a -replace github.com/niftynei/glightning v0.8.2 => github.com/breez/glightning v0.0.0-20221124075140-383be4672b47 +replace github.com/niftynei/glightning v0.8.2 => github.com/breez/glightning v0.0.0-20221201092709-02feadd1d0ad diff --git a/itest/intercept_zero_conf_test.go b/itest/intercept_zero_conf_test.go index 2318e56..d6312c1 100644 --- a/itest/intercept_zero_conf_test.go +++ b/itest/intercept_zero_conf_test.go @@ -2,7 +2,6 @@ package itest import ( "log" - "testing" "time" "github.com/breez/lntest" @@ -10,24 +9,18 @@ import ( "gotest.tools/assert" ) -func TestOpenZeroConfChannelOnReceive(t *testing.T) { - harness := lntest.NewTestHarness(t) - defer harness.TearDown() - - timeout := time.Now().Add(time.Minute) - - miner := lntest.NewMiner(harness) - alice := lntest.NewCoreLightningNode(harness, miner, "Alice", timeout) - bob := NewZeroConfNode(harness, miner, "Bob", timeout) - lsp := NewLspdNode(harness, miner, "Lsp", timeout) +func testOpenZeroConfChannelOnReceive(h *lntest.TestHarness, lsp LspNode, miner *lntest.Miner, timeout time.Time) { + alice := lntest.NewCoreLightningNode(h, miner, "Alice", timeout) + bob := NewZeroConfNode(h, miner, "Bob", timeout) alice.Fund(10000000, timeout) - lsp.lightningNode.Fund(10000000, timeout) + lsp.LightningNode().Fund(10000000, timeout) log.Print("Opening channel between Alice and the lsp") - alice.OpenChannelAndWait(lsp.lightningNode, &lntest.OpenChannelOptions{ + channel := alice.OpenChannel(lsp.LightningNode(), &lntest.OpenChannelOptions{ AmountSat: 1000000, - }, timeout) + }) + alice.WaitForChannelReady(channel, timeout) log.Printf("Adding bob's invoices") outerAmountMsat := uint64(2100000) @@ -41,12 +34,12 @@ func TestOpenZeroConfChannelOnReceive(t *testing.T) { }) log.Print("Connecting bob to lspd") - bob.lightningNode.ConnectPeer(lsp.lightningNode) + bob.lightningNode.ConnectPeer(lsp.LightningNode()) // NOTE: We pretend to be paying fees to the lsp, but actually we won't. log.Printf("Registering payment with lsp") pretendAmount := outerAmountMsat - 2000000 - lsp.RegisterPayment(&lspd.PaymentInformation{ + RegisterPayment(lsp, &lspd.PaymentInformation{ PaymentHash: innerInvoice.paymentHash, PaymentSecret: innerInvoice.paymentSecret, Destination: bob.lightningNode.NodeId(), @@ -58,28 +51,22 @@ func TestOpenZeroConfChannelOnReceive(t *testing.T) { payResp := alice.Pay(outerInvoice.bolt11, timeout) bobInvoice := bob.lightningNode.GetInvoice(payResp.PaymentHash) - assert.DeepEqual(t, payResp.PaymentPreimage, bobInvoice.PaymentPreimage) - assert.Equal(t, outerAmountMsat, bobInvoice.AmountReceivedMsat) + assert.DeepEqual(h.T, payResp.PaymentPreimage, bobInvoice.PaymentPreimage) + assert.Equal(h.T, outerAmountMsat, bobInvoice.AmountReceivedMsat) } -func TestOpenZeroConfSingleHtlc(t *testing.T) { - harness := lntest.NewTestHarness(t) - defer harness.TearDown() - - timeout := time.Now().Add(time.Minute) - - miner := lntest.NewMiner(harness) - alice := lntest.NewCoreLightningNode(harness, miner, "Alice", timeout) - bob := NewZeroConfNode(harness, miner, "Bob", timeout) - lsp := NewLspdNode(harness, miner, "Lsp", timeout) +func testOpenZeroConfSingleHtlc(h *lntest.TestHarness, lsp LspNode, miner *lntest.Miner, timeout time.Time) { + alice := lntest.NewCoreLightningNode(h, miner, "Alice", timeout) + bob := NewZeroConfNode(h, miner, "Bob", timeout) alice.Fund(10000000, timeout) - lsp.lightningNode.Fund(10000000, timeout) + lsp.LightningNode().Fund(10000000, timeout) log.Print("Opening channel between Alice and the lsp") - channel := alice.OpenChannelAndWait(lsp.lightningNode, &lntest.OpenChannelOptions{ + channel := alice.OpenChannel(lsp.LightningNode(), &lntest.OpenChannelOptions{ AmountSat: 1000000, - }, timeout) + }) + channelId := alice.WaitForChannelReady(channel, timeout) log.Printf("Adding bob's invoices") outerAmountMsat := uint64(2100000) @@ -93,12 +80,12 @@ func TestOpenZeroConfSingleHtlc(t *testing.T) { }) log.Print("Connecting bob to lspd") - bob.lightningNode.ConnectPeer(lsp.lightningNode) + bob.lightningNode.ConnectPeer(lsp.LightningNode()) // NOTE: We pretend to be paying fees to the lsp, but actually we won't. log.Printf("Registering payment with lsp") pretendAmount := outerAmountMsat - 2000000 - lsp.RegisterPayment(&lspd.PaymentInformation{ + RegisterPayment(lsp, &lspd.PaymentInformation{ PaymentHash: innerInvoice.paymentHash, PaymentSecret: innerInvoice.paymentSecret, Destination: bob.lightningNode.NodeId(), @@ -107,23 +94,23 @@ func TestOpenZeroConfSingleHtlc(t *testing.T) { }) log.Printf("Alice paying") - route := constructRoute(lsp.lightningNode, bob.lightningNode, channel.ChannelId, "1x0x0", outerAmountMsat) - alice.StartPayPartViaRoute(outerAmountMsat, outerInvoice.paymentHash, outerInvoice.paymentSecret, 0, route) - payResp := alice.WaitForPaymentPart(outerInvoice.paymentHash, timeout, 0) + route := constructRoute(lsp.LightningNode(), bob.lightningNode, channelId, lntest.NewShortChanIDFromString("1x0x0"), outerAmountMsat) + payResp := alice.PayViaRoute(outerAmountMsat, outerInvoice.paymentHash, outerInvoice.paymentSecret, route, timeout) bobInvoice := bob.lightningNode.GetInvoice(payResp.PaymentHash) - assert.DeepEqual(t, payResp.PaymentPreimage, bobInvoice.PaymentPreimage) - assert.Equal(t, outerAmountMsat, bobInvoice.AmountReceivedMsat) + assert.DeepEqual(h.T, payResp.PaymentPreimage, bobInvoice.PaymentPreimage) + assert.Equal(h.T, outerAmountMsat, bobInvoice.AmountReceivedMsat) } func constructRoute( - lsp *lntest.CoreLightningNode, - bob *lntest.CoreLightningNode, - aliceLspChannel string, - lspBobChannel string, - amountMsat uint64) *lntest.Route { + lsp lntest.LightningNode, + bob lntest.LightningNode, + aliceLspChannel lntest.ShortChannelID, + lspBobChannel lntest.ShortChannelID, + amountMsat uint64, +) *lntest.Route { return &lntest.Route{ - Route: []*lntest.Hop{ + Hops: []*lntest.Hop{ { Id: lsp.NodeId(), Channel: aliceLspChannel, diff --git a/itest/lspd_node.go b/itest/lspd_node.go index dc76539..73c024d 100644 --- a/itest/lspd_node.go +++ b/itest/lspd_node.go @@ -9,12 +9,14 @@ import ( "os" "os/exec" "path/filepath" + "strings" "time" "github.com/breez/lntest" "github.com/breez/lspd/btceclegacy" lspd "github.com/breez/lspd/rpc" "github.com/btcsuite/btcd/btcec/v2" + "github.com/decred/dcrd/dcrec/secp256k1/v4" "github.com/golang/protobuf/proto" grpc "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" @@ -35,28 +37,208 @@ var ( lspCltvDelta uint16 = 40 ) -type LspNode struct { +type LspNode interface { + Harness() *lntest.TestHarness + PublicKey() *btcec.PublicKey + Rpc() lspd.ChannelOpenerClient + NodeId() []byte + LightningNode() lntest.LightningNode +} + +type ClnLspNode struct { harness *lntest.TestHarness lightningNode *lntest.CoreLightningNode rpc lspd.ChannelOpenerClient - rpcPort uint32 - rpcHost string - privateKey btcec.PrivateKey publicKey btcec.PublicKey postgresBackend *PostgresContainer - scriptDir string } -func NewLspdNode(h *lntest.TestHarness, m *lntest.Miner, name string, timeout time.Time) *LspNode { +func (c *ClnLspNode) Harness() *lntest.TestHarness { + return c.harness +} + +func (c *ClnLspNode) PublicKey() *btcec.PublicKey { + return &c.publicKey +} + +func (c *ClnLspNode) Rpc() lspd.ChannelOpenerClient { + return c.rpc +} + +func (l *ClnLspNode) TearDown() error { + // NOTE: The lightningnode will be torn down on its own. + return l.postgresBackend.Shutdown(l.harness.Ctx) +} + +func (l *ClnLspNode) Cleanup() error { + return l.postgresBackend.Cleanup(l.harness.Ctx) +} + +func (l *ClnLspNode) NodeId() []byte { + return l.lightningNode.NodeId() +} + +func (l *ClnLspNode) LightningNode() lntest.LightningNode { + return l.lightningNode +} + +type LndLspNode struct { + harness *lntest.TestHarness + lightningNode *lntest.LndNode + rpc lspd.ChannelOpenerClient + publicKey btcec.PublicKey + postgresBackend *PostgresContainer + logFile *os.File + lspdCmd *exec.Cmd +} + +func (c *LndLspNode) Harness() *lntest.TestHarness { + return c.harness +} + +func (c *LndLspNode) PublicKey() *btcec.PublicKey { + return &c.publicKey +} + +func (c *LndLspNode) Rpc() lspd.ChannelOpenerClient { + return c.rpc +} + +func (l *LndLspNode) TearDown() error { + // NOTE: The lightningnode will be torn down on its own. + if l.lspdCmd != nil && l.lspdCmd.Process != nil { + err := l.lspdCmd.Process.Kill() + if err != nil { + log.Printf("error stopping lspd process: %v", err) + } + } + + if l.logFile != nil { + err := l.logFile.Close() + if err != nil { + log.Printf("error closing logfile: %v", err) + } + } + + return l.postgresBackend.Shutdown(l.harness.Ctx) +} + +func (l *LndLspNode) Cleanup() error { + return l.postgresBackend.Cleanup(l.harness.Ctx) +} + +func (l *LndLspNode) NodeId() []byte { + return l.lightningNode.NodeId() +} + +func (l *LndLspNode) LightningNode() lntest.LightningNode { + return l.lightningNode +} + +func NewClnLspdNode(h *lntest.TestHarness, m *lntest.Miner, name string, timeout time.Time) LspNode { + scriptFilePath, grpcAddress, publ, postgresBackend := setupLspd(h, name, "RUN_CLN=true") + args := []string{ + fmt.Sprintf("--plugin=%s", scriptFilePath), + fmt.Sprintf("--fee-base=%d", lspBaseFeeMsat), + fmt.Sprintf("--fee-per-satoshi=%d", lspFeeRatePpm), + fmt.Sprintf("--cltv-delta=%d", lspCltvDelta), + } + + lightningNode := lntest.NewCoreLightningNode(h, m, name, timeout, args...) + + conn, err := grpc.Dial( + grpcAddress, + grpc.WithTransportCredentials(insecure.NewCredentials()), + grpc.WithPerRPCCredentials(&token{token: "hello"}), + ) + lntest.CheckError(h.T, err) + + client := lspd.NewChannelOpenerClient(conn) + + lspNode := &ClnLspNode{ + harness: h, + lightningNode: lightningNode, + rpc: client, + publicKey: *publ, + postgresBackend: postgresBackend, + } + + h.AddStoppable(lspNode) + h.AddCleanable(lspNode) + return lspNode +} + +func NewLndLspdNode(h *lntest.TestHarness, m *lntest.Miner, name string, timeout time.Time) LspNode { + args := []string{ + "--protocol.zero-conf", + "--protocol.option-scid-alias", + "--requireinterceptor", + "--bitcoin.defaultchanconfs=0", + "--bitcoin.chanreservescript=\"0\"", + fmt.Sprintf("--bitcoin.basefee=%d", lspBaseFeeMsat), + fmt.Sprintf("--bitcoin.feerate=%d", lspFeeRatePpm), + fmt.Sprintf("--bitcoin.timelockdelta=%d", lspCltvDelta), + } + + lightningNode := lntest.NewLndNode(h, m, name, timeout, args...) + tlsCert := strings.Replace(string(lightningNode.TlsCert()), "\n", "\\n", -1) + scriptFilePath, grpcAddress, publ, postgresBackend := setupLspd(h, name, + "RUN_LND=true", + fmt.Sprintf("LND_CERT=\"%s\"", tlsCert), + fmt.Sprintf("LND_ADDRESS=%s", lightningNode.GrpcHost()), + fmt.Sprintf("LND_MACAROON_HEX=%x", lightningNode.Macaroon()), + ) + scriptDir := filepath.Dir(scriptFilePath) + logFilePath := filepath.Join(scriptDir, "lspd.log") + h.RegisterLogfile(logFilePath, fmt.Sprintf("lspd-%s", name)) + + lspdCmd := exec.CommandContext(h.Ctx, scriptFilePath) + logFile, err := os.Create(logFilePath) + lntest.CheckError(h.T, err) + + lspdCmd.Stdout = logFile + lspdCmd.Stderr = logFile + + log.Printf("%s: starting lspd %s", name, scriptFilePath) + err = lspdCmd.Start() + lntest.CheckError(h.T, err) + + conn, err := grpc.Dial( + grpcAddress, + grpc.WithTransportCredentials(insecure.NewCredentials()), + grpc.WithPerRPCCredentials(&token{token: "hello"}), + ) + lntest.CheckError(h.T, err) + + client := lspd.NewChannelOpenerClient(conn) + + lspNode := &LndLspNode{ + harness: h, + lightningNode: lightningNode, + rpc: client, + publicKey: *publ, + postgresBackend: postgresBackend, + logFile: logFile, + lspdCmd: lspdCmd, + } + + h.AddStoppable(lspNode) + h.AddCleanable(lspNode) + return lspNode +} + +func setupLspd(h *lntest.TestHarness, name string, envExt ...string) (string, string, *secp256k1.PublicKey, *PostgresContainer) { scriptDir := h.GetDirectory(fmt.Sprintf("lspd-%s", name)) - migrationsDir, err := GetMigrationsDir() + log.Printf("%s: Creating LSPD in dir %s", name, scriptDir) + migrationsDir, err := getMigrationsDir() lntest.CheckError(h.T, err) pgLogfile := filepath.Join(scriptDir, "postgres.log") + h.RegisterLogfile(pgLogfile, fmt.Sprintf("%s-postgres", name)) postgresBackend := StartPostgresContainer(h.T, h.Ctx, pgLogfile) postgresBackend.RunMigrations(h.T, h.Ctx, migrationsDir) - lspdBinary, err := GetLspdBinary() + lspdBinary, err := getLspdBinary() lntest.CheckError(h.T, err) lspdPort, err := lntest.GetPort() @@ -65,23 +247,23 @@ func NewLspdNode(h *lntest.TestHarness, m *lntest.Miner, name string, timeout ti lspdPrivateKeyBytes, err := GenerateRandomBytes(32) lntest.CheckError(h.T, err) - priv, publ := btcec.PrivKeyFromBytes(lspdPrivateKeyBytes) - + _, publ := btcec.PrivKeyFromBytes(lspdPrivateKeyBytes) host := "localhost" grpcAddress := fmt.Sprintf("%s:%d", host, lspdPort) env := []string{ "NODE_NAME=lsp", "NODE_PUBKEY=dunno", "NODE_HOST=host:port", - "RUN_CLN=true", "TOKEN=hello", fmt.Sprintf("DATABASE_URL=%s", postgresBackend.ConnectionString()), fmt.Sprintf("LISTEN_ADDRESS=%s", grpcAddress), fmt.Sprintf("LSPD_PRIVATE_KEY=%x", lspdPrivateKeyBytes), } + env = append(env, envExt...) + scriptFilePath := filepath.Join(scriptDir, "start-lspd.sh") - log.Printf("Creating lspd startup script at %s", scriptFilePath) + log.Printf("%s: Creating lspd startup script at %s", name, scriptFilePath) scriptFile, err := os.OpenFile(scriptFilePath, os.O_CREATE|os.O_WRONLY, 0755) lntest.CheckError(h.T, err) @@ -101,73 +283,27 @@ func NewLspdNode(h *lntest.TestHarness, m *lntest.Miner, name string, timeout ti lntest.CheckError(h.T, err) scriptFile.Close() - args := []string{ - fmt.Sprintf("--plugin=%s", scriptFilePath), - fmt.Sprintf("--fee-base=%d", lspBaseFeeMsat), - fmt.Sprintf("--fee-per-satoshi=%d", lspFeeRatePpm), - fmt.Sprintf("--cltv-delta=%d", lspCltvDelta), - } - - lightningNode := lntest.NewCoreLightningNode(h, m, name, timeout, args...) - - conn, err := grpc.Dial( - grpcAddress, - grpc.WithTransportCredentials(insecure.NewCredentials()), - grpc.WithPerRPCCredentials(&token{token: "hello"}), - ) - lntest.CheckError(h.T, err) - - client := lspd.NewChannelOpenerClient(conn) - - lspNode := &LspNode{ - harness: h, - lightningNode: lightningNode, - rpc: client, - rpcPort: lspdPort, - rpcHost: host, - privateKey: *priv, - publicKey: *publ, - postgresBackend: postgresBackend, - scriptDir: scriptDir, - } - - h.AddStoppable(lspNode) - h.AddCleanable(lspNode) - h.RegisterLogfile(pgLogfile, fmt.Sprintf("%s-postgres", name)) - return lspNode + return scriptFilePath, grpcAddress, publ, postgresBackend } -func (l *LspNode) RegisterPayment(paymentInfo *lspd.PaymentInformation) { +func RegisterPayment(l LspNode, paymentInfo *lspd.PaymentInformation) { serialized, err := proto.Marshal(paymentInfo) - lntest.CheckError(l.harness.T, err) + lntest.CheckError(l.Harness().T, err) - encrypted, err := btceclegacy.Encrypt(&l.publicKey, serialized) - lntest.CheckError(l.harness.T, err) + encrypted, err := btceclegacy.Encrypt(l.PublicKey(), serialized) + lntest.CheckError(l.Harness().T, err) log.Printf("Registering payment") - _, err = l.rpc.RegisterPayment( - l.harness.Ctx, + _, err = l.Rpc().RegisterPayment( + l.Harness().Ctx, &lspd.RegisterPaymentRequest{ Blob: encrypted, }, ) - lntest.CheckError(l.harness.T, err) + lntest.CheckError(l.Harness().T, err) } -func (l *LspNode) TearDown() error { - // NOTE: The lightningnode will be torn down on its own. - return l.postgresBackend.Shutdown(l.harness.Ctx) -} - -func (l *LspNode) Cleanup() error { - return l.postgresBackend.Cleanup(l.harness.Ctx) -} - -func (l *LspNode) NodeId() []byte { - return l.lightningNode.NodeId() -} - -func GetLspdBinary() (string, error) { +func getLspdBinary() (string, error) { if lspdExecutable != nil { return *lspdExecutable, nil } @@ -175,7 +311,7 @@ func GetLspdBinary() (string, error) { return exec.LookPath("lspd") } -func GetMigrationsDir() (string, error) { +func getMigrationsDir() (string, error) { if lspdMigrationsDir != nil { return *lspdMigrationsDir, nil } diff --git a/itest/lspd_test.go b/itest/lspd_test.go new file mode 100644 index 0000000..5c0cb7d --- /dev/null +++ b/itest/lspd_test.go @@ -0,0 +1,70 @@ +package itest + +import ( + "fmt" + "log" + "testing" + "time" + + "github.com/breez/lntest" +) + +var defaultTimeout time.Duration = time.Second * 120 + +func TestLspd(t *testing.T) { + testCases := allTestCases + // runTests(t, testCases, "LND-lspd", func(h *lntest.TestHarness, m *lntest.Miner, t time.Time) LspNode { + // return NewLndLspdNode(h, m, "lsp", t) + // }) + + runTests(t, testCases, "CLN-lspd", func(h *lntest.TestHarness, m *lntest.Miner, t time.Time) LspNode { + return NewClnLspdNode(h, m, "lsp", t) + }) +} + +func runTests(t *testing.T, testCases []*testCase, prefix string, lspFunc func(h *lntest.TestHarness, m *lntest.Miner, t time.Time) LspNode) { + for _, testCase := range testCases { + testCase := testCase + t.Run(fmt.Sprintf("%s: %s", prefix, testCase.name), func(t *testing.T) { + runTest(t, testCase, prefix, lspFunc) + }) + } +} + +func runTest(t *testing.T, testCase *testCase, prefix string, lspFunc func(h *lntest.TestHarness, m *lntest.Miner, t time.Time) LspNode) { + log.Printf("%s: Running test case '%s'", prefix, testCase.name) + harness := lntest.NewTestHarness(t) + defer harness.TearDown() + + var dd time.Duration + to := testCase.timeout + if to == dd { + to = defaultTimeout + } + + timeout := time.Now().Add(to) + log.Printf("Using timeout %v", timeout.String()) + log.Printf("Creating miner") + miner := lntest.NewMiner(harness) + log.Printf("Creating lsp") + lsp := lspFunc(harness, miner, timeout) + log.Printf("Run testcase") + testCase.test(harness, lsp, miner, timeout) +} + +type testCase struct { + name string + test func(h *lntest.TestHarness, lsp LspNode, miner *lntest.Miner, timeout time.Time) + timeout time.Duration +} + +var allTestCases = []*testCase{ + { + name: "testOpenZeroConfChannelOnReceive", + test: testOpenZeroConfChannelOnReceive, + }, + { + name: "testOpenZeroConfSingleHtlc", + test: testOpenZeroConfSingleHtlc, + }, +} diff --git a/itest/postgres.go b/itest/postgres.go index 91539e0..1d4fbfd 100644 --- a/itest/postgres.go +++ b/itest/postgres.go @@ -83,6 +83,13 @@ func StartPostgresContainer(t *testing.T, ctx context.Context, logfile string) * err = cli.ContainerStart(ctx, createResp.ID, types.ContainerStartOptions{}) lntest.CheckError(t, err) + ct := &PostgresContainer{ + id: createResp.ID, + password: "pgpassword", + port: port, + cli: cli, + } + HealthCheck: for { inspect, err := cli.ContainerInspect(ctx, createResp.ID) @@ -93,17 +100,18 @@ HealthCheck: case "unhealthy": lntest.CheckError(t, errors.New("container unhealthy")) case "healthy": - break HealthCheck - default: - time.Sleep(500 * time.Millisecond) - } - } + for { + pgxPool, err := pgxpool.Connect(context.Background(), ct.ConnectionString()) + if err == nil { + pgxPool.Close() + break HealthCheck + } - ct := &PostgresContainer{ - id: createResp.ID, - password: "pgpassword", - port: port, - cli: cli, + time.Sleep(50 * time.Millisecond) + } + default: + time.Sleep(200 * time.Millisecond) + } } go ct.monitorLogs(logfile) diff --git a/itest/zero_conf_node.go b/itest/zero_conf_node.go index ef7c1db..e90c7f6 100644 --- a/itest/zero_conf_node.go +++ b/itest/zero_conf_node.go @@ -4,7 +4,6 @@ import ( "bufio" "crypto/sha256" "fmt" - "log" "os" "path/filepath" "time" @@ -108,7 +107,7 @@ type generateInvoicesRequest struct { innerAmountMsat uint64 outerAmountMsat uint64 description string - lsp *LspNode + lsp LspNode } type invoice struct { @@ -116,17 +115,15 @@ type invoice struct { paymentHash []byte paymentSecret []byte paymentPreimage []byte - expiresAt uint64 } func (n *ZeroConfNode) GenerateInvoices(req generateInvoicesRequest) (invoice, invoice) { preimage, err := GenerateRandomBytes(32) lntest.CheckError(n.harness.T, err) - lspNodeId, err := btcec.ParsePubKey(req.lsp.lightningNode.NodeId()) + lspNodeId, err := btcec.ParsePubKey(req.lsp.NodeId()) lntest.CheckError(n.harness.T, err) - log.Printf("Adding bob's invoices") innerInvoice := n.lightningNode.CreateBolt11Invoice(&lntest.CreateInvoiceOptions{ AmountMsat: req.innerAmountMsat, Description: &req.description, @@ -161,14 +158,12 @@ func (n *ZeroConfNode) GenerateInvoices(req generateInvoicesRequest) (invoice, i paymentHash: innerInvoice.PaymentHash, paymentSecret: innerInvoice.PaymentSecret, paymentPreimage: preimage, - expiresAt: innerInvoice.ExpiresAt, } outer := invoice{ bolt11: outerInvoice, paymentHash: outerInvoiceRaw.PaymentHash[:], paymentSecret: innerInvoice.PaymentSecret, paymentPreimage: preimage, - expiresAt: innerInvoice.ExpiresAt, } return inner, outer From e31a4c43f30670c46166e49ea45a14be722b6173 Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Sat, 3 Dec 2022 10:47:47 +0100 Subject: [PATCH 097/214] reusable open channel logic and share macaroon --- cln_client.go | 10 +- cln_interceptor.go | 58 ++------- forwarding_history.go | 11 +- intercept.go | 253 ++++++++++++++++++++++--------------- lightning_client.go | 2 +- lnd_client.go | 31 +++-- lnd_interceptor.go | 107 ++++++---------- lnd_macaroon_credential.go | 25 ++++ 8 files changed, 249 insertions(+), 248 deletions(-) create mode 100644 lnd_macaroon_credential.go diff --git a/cln_client.go b/cln_client.go index 64d88f7..8dcca89 100644 --- a/cln_client.go +++ b/cln_client.go @@ -43,25 +43,23 @@ func (c *ClnClient) GetInfo() (*GetInfoResult, error) { }, nil } -func (c *ClnClient) IsConnected(destination []byte) (*bool, error) { +func (c *ClnClient) IsConnected(destination []byte) (bool, error) { pubKey := hex.EncodeToString(destination) peers, err := c.client.ListPeers() if err != nil { log.Printf("CLN: client.ListPeers() error: %v", err) - return nil, fmt.Errorf("CLN: client.ListPeers() error: %w", err) + return false, fmt.Errorf("CLN: client.ListPeers() error: %w", err) } for _, peer := range peers { if pubKey == peer.Id { log.Printf("destination online: %x", destination) - result := true - return &result, nil + return true, nil } } log.Printf("CLN: destination offline: %x", destination) - result := false - return &result, nil + return false, nil } func (c *ClnClient) OpenChannel(req *OpenChannelRequest) (*wire.OutPoint, error) { diff --git a/cln_interceptor.go b/cln_interceptor.go index a64e727..7f962eb 100644 --- a/cln_interceptor.go +++ b/cln_interceptor.go @@ -8,7 +8,6 @@ import ( "log" "os" "sync" - "time" sphinx "github.com/lightningnetwork/lightning-onion" "github.com/lightningnetwork/lnd/record" @@ -100,8 +99,8 @@ func (i *ClnHtlcInterceptor) OnHtlcAccepted(event *glightning.HtlcAcceptedEvent) interceptResult := intercept(paymentHashBytes, onion.ForwardAmount, uint32(event.Htlc.CltvExpiry)) switch interceptResult.action { - case INTERCEPT_RESUME_OR_CANCEL: - return i.resumeOrCancel(event, interceptResult), nil + case INTERCEPT_RESUME_WITH_ONION: + return i.resumeWithOnion(event, interceptResult), nil case INTERCEPT_FAIL_HTLC: return event.Fail(uint16(FAILURE_TEMPORARY_CHANNEL_FAILURE)), nil case INTERCEPT_FAIL_HTLC_WITH_CODE: @@ -113,51 +112,16 @@ func (i *ClnHtlcInterceptor) OnHtlcAccepted(event *glightning.HtlcAcceptedEvent) } } -func (i *ClnHtlcInterceptor) resumeOrCancel(event *glightning.HtlcAcceptedEvent, interceptResult interceptResult) *glightning.HtlcAcceptedResponse { - deadline := time.Now().Add(60 * time.Second) - - for { - chanResult, _ := i.client.GetChannel(interceptResult.destination, *interceptResult.channelPoint) - if chanResult != nil { - log.Printf("channel opended successfully alias: %v, confirmed: %v", chanResult.InitialChannelID.ToString(), chanResult.ConfirmedChannelID.ToString()) - - err := insertChannel( - uint64(chanResult.InitialChannelID), - uint64(chanResult.ConfirmedChannelID), - interceptResult.channelPoint.String(), - interceptResult.destination, - time.Now(), - ) - - if err != nil { - log.Printf("insertChannel error: %v", err) - return event.Fail(uint16(FAILURE_TEMPORARY_CHANNEL_FAILURE)) - } - - channelID := uint64(chanResult.ConfirmedChannelID) - if channelID == 0 { - channelID = uint64(chanResult.InitialChannelID) - } - //decoding and encoding onion with alias in type 6 record. - newPayload, err := encodePayloadWithNextHop(event.Onion.Payload, channelID) - if err != nil { - log.Printf("encodePayloadWithNextHop error: %v", err) - return event.Fail(uint16(FAILURE_TEMPORARY_CHANNEL_FAILURE)) - } - - log.Printf("forwarding htlc to the destination node and a new private channel was opened") - return event.ContinueWithPayload(newPayload) - } - - log.Printf("waiting for channel to get opened.... %v\n", interceptResult.destination) - if time.Now().After(deadline) { - log.Printf("Stop retrying getChannel(%v, %v)", interceptResult.destination, interceptResult.channelPoint.String()) - break - } - time.Sleep(1 * time.Second) +func (i *ClnHtlcInterceptor) resumeWithOnion(event *glightning.HtlcAcceptedEvent, interceptResult interceptResult) *glightning.HtlcAcceptedResponse { + //decoding and encoding onion with alias in type 6 record. + newPayload, err := encodePayloadWithNextHop(event.Onion.Payload, interceptResult.channelId) + if err != nil { + log.Printf("encodePayloadWithNextHop error: %v", err) + return event.Fail(uint16(FAILURE_TEMPORARY_CHANNEL_FAILURE)) } - log.Printf("Error: Channel failed to opened... timed out. ") - return event.Fail(uint16(FAILURE_TEMPORARY_CHANNEL_FAILURE)) + + log.Printf("forwarding htlc to the destination node and a new private channel was opened") + return event.ContinueWithPayload(newPayload) } func encodePayloadWithNextHop(payloadHex string, channelId uint64) (string, error) { diff --git a/forwarding_history.go b/forwarding_history.go index 997c6b5..d511ba5 100644 --- a/forwarding_history.go +++ b/forwarding_history.go @@ -5,13 +5,11 @@ import ( "encoding/hex" "fmt" "log" - "os" "time" "github.com/lightningnetwork/lnd/htlcswitch/hop" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/chainrpc" - "google.golang.org/grpc/metadata" ) type copyFromEvents struct { @@ -42,8 +40,7 @@ func channelsSynchronize(client *LndClient) { lastSync := time.Now().Add(-6 * time.Minute) for { cancellableCtx, cancel := context.WithCancel(context.Background()) - clientCtx := metadata.AppendToOutgoingContext(cancellableCtx, "macaroon", os.Getenv("LND_MACAROON_HEX")) - stream, err := client.chainNotifierClient.RegisterBlockEpochNtfn(clientCtx, &chainrpc.BlockEpoch{}) + stream, err := client.chainNotifierClient.RegisterBlockEpochNtfn(cancellableCtx, &chainrpc.BlockEpoch{}) if err != nil { log.Printf("chainNotifierClient.RegisterBlockEpochNtfn(): %v", err) cancel() @@ -70,8 +67,7 @@ func channelsSynchronize(client *LndClient) { func channelsSynchronizeOnce(client *LndClient) error { log.Printf("channelsSynchronizeOnce - begin") - clientCtx := metadata.AppendToOutgoingContext(context.Background(), "macaroon", os.Getenv("LND_MACAROON_HEX")) - channels, err := client.client.ListChannels(clientCtx, &lnrpc.ListChannelsRequest{PrivateOnly: true}) + channels, err := client.client.ListChannels(context.Background(), &lnrpc.ListChannelsRequest{PrivateOnly: true}) if err != nil { log.Printf("ListChannels error: %v", err) return fmt.Errorf("client.ListChannels() error: %w", err) @@ -123,10 +119,9 @@ func forwardingHistorySynchronizeOnce(client *LndClient) error { log.Printf("last2: %v", last) now := time.Now() endTime := uint64(now.Add(time.Hour * 24).Unix()) - clientCtx := metadata.AppendToOutgoingContext(context.Background(), "macaroon", os.Getenv("LND_MACAROON_HEX")) indexOffset := uint32(0) for { - forwardHistory, err := client.client.ForwardingHistory(clientCtx, &lnrpc.ForwardingHistoryRequest{ + forwardHistory, err := client.client.ForwardingHistory(context.Background(), &lnrpc.ForwardingHistoryRequest{ StartTime: uint64(last), EndTime: endTime, NumMaxEvents: 10000, diff --git a/intercept.go b/intercept.go index 9978ada..a7a8fff 100644 --- a/intercept.go +++ b/intercept.go @@ -6,6 +6,7 @@ import ( "fmt" "log" "math/big" + "time" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/wire" @@ -20,7 +21,7 @@ type interceptAction int const ( INTERCEPT_RESUME interceptAction = 0 - INTERCEPT_RESUME_OR_CANCEL interceptAction = 1 + INTERCEPT_RESUME_WITH_ONION interceptAction = 1 INTERCEPT_FAIL_HTLC interceptAction = 2 INTERCEPT_FAIL_HTLC_WITH_CODE interceptAction = 3 ) @@ -40,6 +41,7 @@ type interceptResult struct { destination []byte amountMsat uint64 channelPoint *wire.OutPoint + channelId uint64 onionBlob []byte } @@ -53,114 +55,157 @@ func intercept(reqPaymentHash []byte, reqOutgoingAmountMsat uint64, reqOutgoingE action: INTERCEPT_FAIL_HTLC, }, nil } - log.Printf("paymentHash:%x\npaymentSecret:%x\ndestination:%x\nincomingAmountMsat:%v\noutgoingAmountMsat:%v\n\n", + log.Printf("paymentHash:%x\npaymentSecret:%x\ndestination:%x\nincomingAmountMsat:%v\noutgoingAmountMsat:%v", paymentHash, paymentSecret, destination, incomingAmountMsat, outgoingAmountMsat) - if paymentSecret != nil { - - if channelPoint == nil { - if bytes.Equal(paymentHash, reqPaymentHash) { - channelPoint, err = openChannel(client, reqPaymentHash, destination, incomingAmountMsat) - log.Printf("openChannel(%x, %v) err: %v", destination, incomingAmountMsat, err) - if err != nil { - return interceptResult{ - action: INTERCEPT_FAIL_HTLC, - }, nil - } - } else { //probing - failureCode := FAILURE_TEMPORARY_CHANNEL_FAILURE - isConnected, _ := client.IsConnected(destination) - if err != nil || !*isConnected { - failureCode = FAILURE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS - } - - return interceptResult{ - action: INTERCEPT_FAIL_HTLC_WITH_CODE, - failureCode: failureCode, - }, nil - } - } - - pubKey, err := btcec.ParsePubKey(destination) - if err != nil { - log.Printf("btcec.ParsePubKey(%x): %v", destination, err) - return interceptResult{ - action: INTERCEPT_FAIL_HTLC, - }, nil - } - - sessionKey, err := btcec.NewPrivateKey() - if err != nil { - log.Printf("btcec.NewPrivateKey(): %v", err) - return interceptResult{ - action: INTERCEPT_FAIL_HTLC, - }, nil - } - - var bigProd, bigAmt big.Int - amt := (bigAmt.Div(bigProd.Mul(big.NewInt(outgoingAmountMsat), big.NewInt(int64(reqOutgoingAmountMsat))), big.NewInt(incomingAmountMsat))).Int64() - - var addr [32]byte - copy(addr[:], paymentSecret) - hop := route.Hop{ - AmtToForward: lnwire.MilliSatoshi(amt), - OutgoingTimeLock: reqOutgoingExpiry, - MPP: record.NewMPP(lnwire.MilliSatoshi(outgoingAmountMsat), addr), - CustomRecords: make(record.CustomSet), - } - - var b bytes.Buffer - err = hop.PackHopPayload(&b, uint64(0)) - if err != nil { - log.Printf("hop.PackHopPayload(): %v", err) - return interceptResult{ - action: INTERCEPT_FAIL_HTLC, - }, nil - } - - payload, err := sphinx.NewHopPayload(nil, b.Bytes()) - if err != nil { - log.Printf("sphinx.NewHopPayload(): %v", err) - return interceptResult{ - action: INTERCEPT_FAIL_HTLC, - }, nil - } - - var sphinxPath sphinx.PaymentPath - sphinxPath[0] = sphinx.OnionHop{ - NodePub: *pubKey, - HopPayload: payload, - } - sphinxPacket, err := sphinx.NewOnionPacket( - &sphinxPath, sessionKey, reqPaymentHash, - sphinx.DeterministicPacketFiller, - ) - if err != nil { - log.Printf("sphinx.NewOnionPacket(): %v", err) - return interceptResult{ - action: INTERCEPT_FAIL_HTLC, - }, nil - } - var onionBlob bytes.Buffer - err = sphinxPacket.Encode(&onionBlob) - if err != nil { - log.Printf("sphinxPacket.Encode(): %v", err) - return interceptResult{ - action: INTERCEPT_FAIL_HTLC, - }, nil - } - - return interceptResult{ - action: INTERCEPT_RESUME_OR_CANCEL, - destination: destination, - channelPoint: channelPoint, - amountMsat: uint64(amt), - onionBlob: onionBlob.Bytes(), - }, nil - } else { + if paymentSecret == nil { return interceptResult{ action: INTERCEPT_RESUME, }, nil } + + if channelPoint == nil { + if bytes.Equal(paymentHash, reqPaymentHash) { + channelPoint, err = openChannel(client, reqPaymentHash, destination, incomingAmountMsat) + if err != nil { + log.Printf("openChannel(%x, %v) err: %v", destination, incomingAmountMsat, err) + return interceptResult{ + action: INTERCEPT_FAIL_HTLC, + }, nil + } + } else { //probing + failureCode := FAILURE_TEMPORARY_CHANNEL_FAILURE + isConnected, _ := client.IsConnected(destination) + if err != nil || !isConnected { + failureCode = FAILURE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS + } + + return interceptResult{ + action: INTERCEPT_FAIL_HTLC_WITH_CODE, + failureCode: failureCode, + }, nil + } + } + + pubKey, err := btcec.ParsePubKey(destination) + if err != nil { + log.Printf("btcec.ParsePubKey(%x): %v", destination, err) + return interceptResult{ + action: INTERCEPT_FAIL_HTLC, + }, nil + } + + sessionKey, err := btcec.NewPrivateKey() + if err != nil { + log.Printf("btcec.NewPrivateKey(): %v", err) + return interceptResult{ + action: INTERCEPT_FAIL_HTLC, + }, nil + } + + var bigProd, bigAmt big.Int + amt := (bigAmt.Div(bigProd.Mul(big.NewInt(outgoingAmountMsat), big.NewInt(int64(reqOutgoingAmountMsat))), big.NewInt(incomingAmountMsat))).Int64() + + var addr [32]byte + copy(addr[:], paymentSecret) + hop := route.Hop{ + AmtToForward: lnwire.MilliSatoshi(amt), + OutgoingTimeLock: reqOutgoingExpiry, + MPP: record.NewMPP(lnwire.MilliSatoshi(outgoingAmountMsat), addr), + CustomRecords: make(record.CustomSet), + } + + var b bytes.Buffer + err = hop.PackHopPayload(&b, uint64(0)) + if err != nil { + log.Printf("hop.PackHopPayload(): %v", err) + return interceptResult{ + action: INTERCEPT_FAIL_HTLC, + }, nil + } + + payload, err := sphinx.NewHopPayload(nil, b.Bytes()) + if err != nil { + log.Printf("sphinx.NewHopPayload(): %v", err) + return interceptResult{ + action: INTERCEPT_FAIL_HTLC, + }, nil + } + + var sphinxPath sphinx.PaymentPath + sphinxPath[0] = sphinx.OnionHop{ + NodePub: *pubKey, + HopPayload: payload, + } + sphinxPacket, err := sphinx.NewOnionPacket( + &sphinxPath, sessionKey, reqPaymentHash, + sphinx.DeterministicPacketFiller, + ) + if err != nil { + log.Printf("sphinx.NewOnionPacket(): %v", err) + return interceptResult{ + action: INTERCEPT_FAIL_HTLC, + }, nil + } + var onionBlob bytes.Buffer + err = sphinxPacket.Encode(&onionBlob) + if err != nil { + log.Printf("sphinxPacket.Encode(): %v", err) + return interceptResult{ + action: INTERCEPT_FAIL_HTLC, + }, nil + } + + deadline := time.Now().Add(60 * time.Second) + + for { + chanResult, _ := client.GetChannel(destination, *channelPoint) + if chanResult != nil { + log.Printf("channel opended successfully alias: %v, confirmed: %v", chanResult.InitialChannelID.ToString(), chanResult.ConfirmedChannelID.ToString()) + + err := insertChannel( + uint64(chanResult.InitialChannelID), + uint64(chanResult.ConfirmedChannelID), + channelPoint.String(), + destination, + time.Now(), + ) + + if err != nil { + log.Printf("insertChannel error: %v", err) + return interceptResult{ + action: INTERCEPT_FAIL_HTLC_WITH_CODE, + failureCode: FAILURE_TEMPORARY_CHANNEL_FAILURE, + }, nil + } + + channelID := uint64(chanResult.ConfirmedChannelID) + if channelID == 0 { + channelID = uint64(chanResult.InitialChannelID) + } + + return interceptResult{ + action: INTERCEPT_RESUME_WITH_ONION, + destination: destination, + channelPoint: channelPoint, + channelId: channelID, + amountMsat: uint64(amt), + onionBlob: onionBlob.Bytes(), + }, nil + } + + log.Printf("waiting for channel to get opened.... %v\n", destination) + if time.Now().After(deadline) { + log.Printf("Stop retrying getChannel(%v, %v)", destination, channelPoint.String()) + break + } + time.Sleep(1 * time.Second) + } + + log.Printf("Error: Channel failed to opened... timed out. ") + return interceptResult{ + action: INTERCEPT_FAIL_HTLC_WITH_CODE, + failureCode: FAILURE_TEMPORARY_CHANNEL_FAILURE, + }, nil }) return resp.(interceptResult) diff --git a/lightning_client.go b/lightning_client.go index f26116d..a89a38d 100644 --- a/lightning_client.go +++ b/lightning_client.go @@ -23,7 +23,7 @@ type OpenChannelRequest struct { type LightningClient interface { GetInfo() (*GetInfoResult, error) - IsConnected(destination []byte) (*bool, error) + IsConnected(destination []byte) (bool, error) OpenChannel(req *OpenChannelRequest) (*wire.OutPoint, error) GetChannel(peerID []byte, channelPoint wire.OutPoint) (*GetChannelResult, error) GetNodeChannelCount(nodeID []byte) (int, error) diff --git a/lnd_client.go b/lnd_client.go index bf169cb..f0582d7 100644 --- a/lnd_client.go +++ b/lnd_client.go @@ -33,9 +33,14 @@ func NewLndClient() *LndClient { log.Fatalf("credentials: failed to append certificates") } creds := credentials.NewClientTLSFromCert(cp, "") + macCred := NewMacaroonCredential(os.Getenv("LND_MACAROON_HEX")) // Address of an LND instance - conn, err := grpc.Dial(os.Getenv("LND_ADDRESS"), grpc.WithTransportCredentials(creds)) + conn, err := grpc.Dial( + os.Getenv("LND_ADDRESS"), + grpc.WithTransportCredentials(creds), + grpc.WithPerRPCCredentials(macCred), + ) if err != nil { log.Fatalf("Failed to connect to LND gRPC: %v", err) } @@ -56,7 +61,7 @@ func (c *LndClient) Close() { } func (c *LndClient) GetInfo() (*GetInfoResult, error) { - info, err := c.client.GetInfo(macaroonContext(), &lnrpc.GetInfoRequest{}) + info, err := c.client.GetInfo(context.Background(), &lnrpc.GetInfoRequest{}) if err != nil { log.Printf("LND: client.GetInfo() error: %v", err) return nil, err @@ -68,29 +73,27 @@ func (c *LndClient) GetInfo() (*GetInfoResult, error) { }, nil } -func (c *LndClient) IsConnected(destination []byte) (*bool, error) { +func (c *LndClient) IsConnected(destination []byte) (bool, error) { pubKey := hex.EncodeToString(destination) - r, err := c.client.ListPeers(macaroonContext(), &lnrpc.ListPeersRequest{LatestError: true}) + r, err := c.client.ListPeers(context.Background(), &lnrpc.ListPeersRequest{LatestError: true}) if err != nil { log.Printf("LND: client.ListPeers() error: %v", err) - return nil, fmt.Errorf("LND: client.ListPeers() error: %w", 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) - result := true - return &result, nil + return true, nil } } log.Printf("LND: destination offline: %x", destination) - result := false - return &result, nil + return false, nil } func (c *LndClient) OpenChannel(req *OpenChannelRequest) (*wire.OutPoint, error) { - channelPoint, err := c.client.OpenChannelSync(macaroonContext(), &lnrpc.OpenChannelRequest{ + channelPoint, err := c.client.OpenChannelSync(context.Background(), &lnrpc.OpenChannelRequest{ NodePubkey: req.Destination, LocalFundingAmount: int64(req.CapacitySat), TargetConf: int32(req.TargetConf), @@ -115,7 +118,7 @@ func (c *LndClient) OpenChannel(req *OpenChannelRequest) (*wire.OutPoint, error) } func (c *LndClient) GetChannel(peerID []byte, channelPoint wire.OutPoint) (*GetChannelResult, error) { - r, err := c.client.ListChannels(macaroonContext(), &lnrpc.ListChannelsRequest{Peer: peerID}) + 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 @@ -197,7 +200,7 @@ func (c *LndClient) GetClosedChannels(nodeID string, channelPoints map[string]ui } func (c *LndClient) getWaitingCloseChannels(nodeID string) ([]*lnrpc.PendingChannelsResponse_WaitingCloseChannel, error) { - pendingResponse, err := c.client.PendingChannels(macaroonContext(), &lnrpc.PendingChannelsRequest{}) + pendingResponse, err := c.client.PendingChannels(context.Background(), &lnrpc.PendingChannelsRequest{}) if err != nil { return nil, err } @@ -209,7 +212,3 @@ func (c *LndClient) getWaitingCloseChannels(nodeID string) ([]*lnrpc.PendingChan } return waitingCloseChannels, nil } - -func macaroonContext() context.Context { - return metadata.AppendToOutgoingContext(context.Background(), "macaroon", os.Getenv("LND_MACAROON_HEX")) -} diff --git a/lnd_interceptor.go b/lnd_interceptor.go index b7481e7..ed43d82 100644 --- a/lnd_interceptor.go +++ b/lnd_interceptor.go @@ -4,14 +4,12 @@ import ( "context" "fmt" "log" - "os" "sync" "time" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/routerrpc" "google.golang.org/grpc/codes" - "google.golang.org/grpc/metadata" "google.golang.org/grpc/status" ) @@ -19,6 +17,7 @@ type LndHtlcInterceptor struct { client *LndClient stopRequested bool initWg sync.WaitGroup + doneWg sync.WaitGroup } func NewLndHtlcInterceptor() *LndHtlcInterceptor { @@ -49,14 +48,18 @@ func (i *LndHtlcInterceptor) WaitStarted() LightningClient { } func (i *LndHtlcInterceptor) intercept() error { + defer func() { + log.Printf("LND intercept(): stopping. Waiting for in-progress interceptions to complete.") + i.doneWg.Wait() + }() + for { if i.stopRequested { return nil } cancellableCtx, cancel := context.WithCancel(context.Background()) - clientCtx := metadata.AppendToOutgoingContext(cancellableCtx, "macaroon", os.Getenv("LND_MACAROON_HEX")) - interceptorClient, err := i.client.routerClient.HtlcInterceptor(clientCtx) + interceptorClient, err := i.client.routerClient.HtlcInterceptor(cancellableCtx) if err != nil { log.Printf("routerClient.HtlcInterceptor(): %v", err) cancel() @@ -93,30 +96,40 @@ func (i *LndHtlcInterceptor) intercept() error { request.OnionBlob, ) - interceptResult := intercept(request.PaymentHash, request.OutgoingAmountMsat, request.OutgoingExpiry) - switch interceptResult.action { - case INTERCEPT_RESUME_OR_CANCEL: - go resumeOrCancel(clientCtx, interceptorClient, request.IncomingCircuitKey, - interceptResult) - case INTERCEPT_FAIL_HTLC: - failForwardSend(interceptorClient, request.IncomingCircuitKey) - case INTERCEPT_FAIL_HTLC_WITH_CODE: - interceptorClient.Send(&routerrpc.ForwardHtlcInterceptResponse{ - IncomingCircuitKey: request.IncomingCircuitKey, - Action: routerrpc.ResolveHoldForwardAction_FAIL, - FailureCode: mapFailureCode(interceptResult.failureCode), - }) - case INTERCEPT_RESUME: - fallthrough - default: - interceptorClient.Send(&routerrpc.ForwardHtlcInterceptResponse{ - IncomingCircuitKey: request.IncomingCircuitKey, - Action: routerrpc.ResolveHoldForwardAction_RESUME, - OutgoingAmountMsat: request.OutgoingAmountMsat, - OutgoingRequestedChanId: request.OutgoingRequestedChanId, - OnionBlob: request.OnionBlob, - }) - } + i.doneWg.Add(1) + go func() { + interceptResult := intercept(request.PaymentHash, request.OutgoingAmountMsat, request.OutgoingExpiry) + switch interceptResult.action { + case INTERCEPT_RESUME_WITH_ONION: + interceptorClient.Send(&routerrpc.ForwardHtlcInterceptResponse{ + IncomingCircuitKey: request.IncomingCircuitKey, + Action: routerrpc.ResolveHoldForwardAction_RESUME, + OutgoingAmountMsat: interceptResult.amountMsat, + OutgoingRequestedChanId: uint64(interceptResult.channelId), + OnionBlob: interceptResult.onionBlob, + }) + case INTERCEPT_FAIL_HTLC: + failForwardSend(interceptorClient, request.IncomingCircuitKey) + case INTERCEPT_FAIL_HTLC_WITH_CODE: + interceptorClient.Send(&routerrpc.ForwardHtlcInterceptResponse{ + IncomingCircuitKey: request.IncomingCircuitKey, + Action: routerrpc.ResolveHoldForwardAction_FAIL, + FailureCode: mapFailureCode(interceptResult.failureCode), + }) + case INTERCEPT_RESUME: + fallthrough + default: + interceptorClient.Send(&routerrpc.ForwardHtlcInterceptResponse{ + IncomingCircuitKey: request.IncomingCircuitKey, + Action: routerrpc.ResolveHoldForwardAction_RESUME, + OutgoingAmountMsat: request.OutgoingAmountMsat, + OutgoingRequestedChanId: request.OutgoingRequestedChanId, + OnionBlob: request.OnionBlob, + }) + } + + i.doneWg.Done() + }() } cancel() @@ -140,41 +153,3 @@ func failForwardSend(interceptorClient routerrpc.Router_HtlcInterceptorClient, i Action: routerrpc.ResolveHoldForwardAction_FAIL, }) } - -func resumeOrCancel( - ctx context.Context, - interceptorClient routerrpc.Router_HtlcInterceptorClient, - incomingCircuitKey *routerrpc.CircuitKey, - interceptResult interceptResult, -) { - deadline := time.Now().Add(10 * time.Second) - for { - ch, err := client.GetChannel(interceptResult.destination, *interceptResult.channelPoint) - if err != nil { - failForwardSend(interceptorClient, incomingCircuitKey) - return - } - - if uint64(ch.InitialChannelID) != 0 { - interceptorClient.Send(&routerrpc.ForwardHtlcInterceptResponse{ - IncomingCircuitKey: incomingCircuitKey, - Action: routerrpc.ResolveHoldForwardAction_RESUME, - OutgoingAmountMsat: interceptResult.amountMsat, - OutgoingRequestedChanId: uint64(ch.InitialChannelID), - OnionBlob: interceptResult.onionBlob, - }) - err := insertChannel(uint64(ch.InitialChannelID), uint64(ch.ConfirmedChannelID), interceptResult.channelPoint.String(), interceptResult.destination, time.Now()) - if err != nil { - log.Printf("insertChannel error: %v", err) - } - return - } - log.Printf("getChannel(%x, %v) returns 0", interceptResult.destination, interceptResult.channelPoint.String()) - if time.Now().After(deadline) { - log.Printf("Stop retrying getChannel(%x, %v)", interceptResult.destination, interceptResult.channelPoint.String()) - break - } - time.Sleep(1 * time.Second) - } - failForwardSend(interceptorClient, incomingCircuitKey) -} diff --git a/lnd_macaroon_credential.go b/lnd_macaroon_credential.go new file mode 100644 index 0000000..8e70f87 --- /dev/null +++ b/lnd_macaroon_credential.go @@ -0,0 +1,25 @@ +package main + +import ( + "context" +) + +type MacaroonCredential struct { + MacaroonHex string +} + +func NewMacaroonCredential(hex string) *MacaroonCredential { + return &MacaroonCredential{ + MacaroonHex: hex, + } +} + +func (m *MacaroonCredential) RequireTransportSecurity() bool { + return true +} + +func (m *MacaroonCredential) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) { + md := make(map[string]string) + md["macaroon"] = m.MacaroonHex + return md, nil +} From 0e98769d899a7171888cc5ccf649402cea978b01 Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Wed, 7 Dec 2022 13:34:17 +0100 Subject: [PATCH 098/214] pass forward_to to htlc accepted response --- cln_client.go | 11 ++++++----- cln_interceptor.go | 4 +++- db.go | 2 +- go.mod | 4 ++-- itest/intercept_zero_conf_test.go | 29 ++++++++++++++--------------- itest/lspd_node.go | 13 ++++++------- itest/lspd_test.go | 29 +++++++++++++++-------------- itest/zero_conf_node.go | 4 +--- 8 files changed, 48 insertions(+), 48 deletions(-) diff --git a/cln_client.go b/cln_client.go index 8dcca89..7a47395 100644 --- a/cln_client.go +++ b/cln_client.go @@ -5,6 +5,7 @@ import ( "fmt" "log" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" "github.com/niftynei/glightning/glightning" "golang.org/x/exp/slices" @@ -92,15 +93,15 @@ func (c *ClnClient) OpenChannel(req *OpenChannelRequest) (*wire.OutPoint, error) return nil, err } - fundingTxId, err := hex.DecodeString(fundResult.FundingTxId) + fundingTxId, err := chainhash.NewHashFromStr(fundResult.FundingTxId) if err != nil { - log.Printf("CLN: hex.DecodeString(%s) error: %v", fundResult.FundingTxId, err) + log.Printf("CLN: chainhash.NewHashFromStr(%s) error: %v", fundResult.FundingTxId, err) return nil, err } - channelPoint, err := NewOutPoint(fundingTxId, uint32(fundResult.FundingTxOutputNum)) + channelPoint, err := NewOutPoint(fundingTxId[:], uint32(fundResult.FundingTxOutputNum)) if err != nil { - log.Printf("CLN: NewOutPoint(%x, %d) error: %v", fundingTxId, fundResult.FundingTxOutputNum, err) + log.Printf("CLN: NewOutPoint(%s, %d) error: %v", fundingTxId.String(), fundResult.FundingTxOutputNum, err) return nil, err } @@ -110,12 +111,12 @@ func (c *ClnClient) OpenChannel(req *OpenChannelRequest) (*wire.OutPoint, error) func (c *ClnClient) GetChannel(peerID []byte, channelPoint wire.OutPoint) (*GetChannelResult, error) { pubkey := hex.EncodeToString(peerID) peer, err := c.client.GetPeer(pubkey) - fundingTxID := hex.EncodeToString(channelPoint.Hash[:]) if err != nil { log.Printf("CLN: client.GetPeer(%s) error: %v", pubkey, err) return nil, err } + fundingTxID := channelPoint.Hash.String() for _, c := range peer.Channels { log.Printf("getChannel destination: %s, Short channel id: %v, local alias: %v , FundingTxID:%v, State:%v ", pubkey, c.ShortChannelId, c.Alias.Local, c.FundingTxId, c.State) if slices.Contains(OPEN_STATUSES, c.State) && c.FundingTxId == fundingTxID { diff --git a/cln_interceptor.go b/cln_interceptor.go index 7f962eb..71895ed 100644 --- a/cln_interceptor.go +++ b/cln_interceptor.go @@ -10,6 +10,7 @@ import ( "sync" sphinx "github.com/lightningnetwork/lightning-onion" + "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/record" "github.com/lightningnetwork/lnd/tlv" "github.com/niftynei/glightning/glightning" @@ -120,8 +121,9 @@ func (i *ClnHtlcInterceptor) resumeWithOnion(event *glightning.HtlcAcceptedEvent return event.Fail(uint16(FAILURE_TEMPORARY_CHANNEL_FAILURE)) } + chanId := lnwire.NewChanIDFromOutPoint(interceptResult.channelPoint) log.Printf("forwarding htlc to the destination node and a new private channel was opened") - return event.ContinueWithPayload(newPayload) + return event.ContinueWith(chanId.String(), newPayload) } func encodePayloadWithNextHop(payloadHex string, channelId uint64) (string, error) { diff --git a/db.go b/db.go index 5216a48..4b23902 100644 --- a/db.go +++ b/db.go @@ -62,7 +62,7 @@ func setFundingTx(paymentHash []byte, channelPoint *wire.OutPoint) error { SET funding_tx_id = $2, funding_tx_outnum = $3 WHERE payment_hash=$1`, paymentHash, channelPoint.Hash[:], channelPoint.Index) - log.Printf("setFundingTx(%x, %x, %v): %s err: %v", paymentHash, channelPoint.Hash[:], channelPoint.Index, commandTag, err) + log.Printf("setFundingTx(%x, %s, %d): %s err: %v", paymentHash, channelPoint.Hash.String(), channelPoint.Index, commandTag, err) return err } diff --git a/go.mod b/go.mod index a6b6cf9..d4b13f6 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.19 require ( github.com/aws/aws-sdk-go v1.30.20 - github.com/breez/lntest v0.0.6 + github.com/breez/lntest v0.0.7 github.com/btcsuite/btcd v0.23.1 github.com/btcsuite/btcd/btcec/v2 v2.2.1 github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 @@ -176,4 +176,4 @@ require ( replace github.com/lightningnetwork/lnd v0.15.1-beta => github.com/breez/lnd v0.15.0-beta.rc6.0.20220831104847-00b86a81e57a -replace github.com/niftynei/glightning v0.8.2 => github.com/breez/glightning v0.0.0-20221201092709-02feadd1d0ad +replace github.com/niftynei/glightning v0.8.2 => github.com/breez/glightning v0.0.0-20221207122347-03c2d8cb69dd \ No newline at end of file diff --git a/itest/intercept_zero_conf_test.go b/itest/intercept_zero_conf_test.go index d6312c1..fd40f78 100644 --- a/itest/intercept_zero_conf_test.go +++ b/itest/intercept_zero_conf_test.go @@ -2,25 +2,24 @@ package itest import ( "log" - "time" "github.com/breez/lntest" lspd "github.com/breez/lspd/rpc" "gotest.tools/assert" ) -func testOpenZeroConfChannelOnReceive(h *lntest.TestHarness, lsp LspNode, miner *lntest.Miner, timeout time.Time) { - alice := lntest.NewCoreLightningNode(h, miner, "Alice", timeout) - bob := NewZeroConfNode(h, miner, "Bob", timeout) +func testOpenZeroConfChannelOnReceive(h *lntest.TestHarness, lsp LspNode, miner *lntest.Miner) { + alice := lntest.NewCoreLightningNode(h, miner, "Alice") + bob := NewZeroConfNode(h, miner, "Bob") - alice.Fund(10000000, timeout) - lsp.LightningNode().Fund(10000000, timeout) + alice.Fund(10000000) + lsp.LightningNode().Fund(10000000) log.Print("Opening channel between Alice and the lsp") channel := alice.OpenChannel(lsp.LightningNode(), &lntest.OpenChannelOptions{ AmountSat: 1000000, }) - alice.WaitForChannelReady(channel, timeout) + alice.WaitForChannelReady(channel) log.Printf("Adding bob's invoices") outerAmountMsat := uint64(2100000) @@ -48,25 +47,25 @@ func testOpenZeroConfChannelOnReceive(h *lntest.TestHarness, lsp LspNode, miner }) log.Printf("Alice paying") - payResp := alice.Pay(outerInvoice.bolt11, timeout) + payResp := alice.Pay(outerInvoice.bolt11) bobInvoice := bob.lightningNode.GetInvoice(payResp.PaymentHash) assert.DeepEqual(h.T, payResp.PaymentPreimage, bobInvoice.PaymentPreimage) assert.Equal(h.T, outerAmountMsat, bobInvoice.AmountReceivedMsat) } -func testOpenZeroConfSingleHtlc(h *lntest.TestHarness, lsp LspNode, miner *lntest.Miner, timeout time.Time) { - alice := lntest.NewCoreLightningNode(h, miner, "Alice", timeout) - bob := NewZeroConfNode(h, miner, "Bob", timeout) +func testOpenZeroConfSingleHtlc(h *lntest.TestHarness, lsp LspNode, miner *lntest.Miner) { + alice := lntest.NewCoreLightningNode(h, miner, "Alice") + bob := NewZeroConfNode(h, miner, "Bob") - alice.Fund(10000000, timeout) - lsp.LightningNode().Fund(10000000, timeout) + alice.Fund(10000000) + lsp.LightningNode().Fund(10000000) log.Print("Opening channel between Alice and the lsp") channel := alice.OpenChannel(lsp.LightningNode(), &lntest.OpenChannelOptions{ AmountSat: 1000000, }) - channelId := alice.WaitForChannelReady(channel, timeout) + channelId := alice.WaitForChannelReady(channel) log.Printf("Adding bob's invoices") outerAmountMsat := uint64(2100000) @@ -95,7 +94,7 @@ func testOpenZeroConfSingleHtlc(h *lntest.TestHarness, lsp LspNode, miner *lntes log.Printf("Alice paying") route := constructRoute(lsp.LightningNode(), bob.lightningNode, channelId, lntest.NewShortChanIDFromString("1x0x0"), outerAmountMsat) - payResp := alice.PayViaRoute(outerAmountMsat, outerInvoice.paymentHash, outerInvoice.paymentSecret, route, timeout) + payResp := alice.PayViaRoute(outerAmountMsat, outerInvoice.paymentHash, outerInvoice.paymentSecret, route) bobInvoice := bob.lightningNode.GetInvoice(payResp.PaymentHash) assert.DeepEqual(h.T, payResp.PaymentPreimage, bobInvoice.PaymentPreimage) diff --git a/itest/lspd_node.go b/itest/lspd_node.go index 73c024d..012354c 100644 --- a/itest/lspd_node.go +++ b/itest/lspd_node.go @@ -2,7 +2,7 @@ package itest import ( "bufio" - context "context" + "context" "flag" "fmt" "log" @@ -10,7 +10,6 @@ import ( "os/exec" "path/filepath" "strings" - "time" "github.com/breez/lntest" "github.com/breez/lspd/btceclegacy" @@ -18,7 +17,7 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/decred/dcrd/dcrec/secp256k1/v4" "github.com/golang/protobuf/proto" - grpc "google.golang.org/grpc" + "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" ) @@ -135,7 +134,7 @@ func (l *LndLspNode) LightningNode() lntest.LightningNode { return l.lightningNode } -func NewClnLspdNode(h *lntest.TestHarness, m *lntest.Miner, name string, timeout time.Time) LspNode { +func NewClnLspdNode(h *lntest.TestHarness, m *lntest.Miner, name string) LspNode { scriptFilePath, grpcAddress, publ, postgresBackend := setupLspd(h, name, "RUN_CLN=true") args := []string{ fmt.Sprintf("--plugin=%s", scriptFilePath), @@ -144,7 +143,7 @@ func NewClnLspdNode(h *lntest.TestHarness, m *lntest.Miner, name string, timeout fmt.Sprintf("--cltv-delta=%d", lspCltvDelta), } - lightningNode := lntest.NewCoreLightningNode(h, m, name, timeout, args...) + lightningNode := lntest.NewCoreLightningNode(h, m, name, args...) conn, err := grpc.Dial( grpcAddress, @@ -168,7 +167,7 @@ func NewClnLspdNode(h *lntest.TestHarness, m *lntest.Miner, name string, timeout return lspNode } -func NewLndLspdNode(h *lntest.TestHarness, m *lntest.Miner, name string, timeout time.Time) LspNode { +func NewLndLspdNode(h *lntest.TestHarness, m *lntest.Miner, name string) LspNode { args := []string{ "--protocol.zero-conf", "--protocol.option-scid-alias", @@ -180,7 +179,7 @@ func NewLndLspdNode(h *lntest.TestHarness, m *lntest.Miner, name string, timeout fmt.Sprintf("--bitcoin.timelockdelta=%d", lspCltvDelta), } - lightningNode := lntest.NewLndNode(h, m, name, timeout, args...) + lightningNode := lntest.NewLndNode(h, m, name, args...) tlsCert := strings.Replace(string(lightningNode.TlsCert()), "\n", "\\n", -1) scriptFilePath, grpcAddress, publ, postgresBackend := setupLspd(h, name, "RUN_LND=true", diff --git a/itest/lspd_test.go b/itest/lspd_test.go index 5c0cb7d..1e5b092 100644 --- a/itest/lspd_test.go +++ b/itest/lspd_test.go @@ -13,16 +13,16 @@ var defaultTimeout time.Duration = time.Second * 120 func TestLspd(t *testing.T) { testCases := allTestCases - // runTests(t, testCases, "LND-lspd", func(h *lntest.TestHarness, m *lntest.Miner, t time.Time) LspNode { - // return NewLndLspdNode(h, m, "lsp", t) + // runTests(t, testCases, "LND-lspd", func(h *lntest.TestHarness, m *lntest.Miner) LspNode { + // return NewLndLspdNode(h, m, "lsp") // }) - runTests(t, testCases, "CLN-lspd", func(h *lntest.TestHarness, m *lntest.Miner, t time.Time) LspNode { - return NewClnLspdNode(h, m, "lsp", t) + runTests(t, testCases, "CLN-lspd", func(h *lntest.TestHarness, m *lntest.Miner) LspNode { + return NewClnLspdNode(h, m, "lsp") }) } -func runTests(t *testing.T, testCases []*testCase, prefix string, lspFunc func(h *lntest.TestHarness, m *lntest.Miner, t time.Time) LspNode) { +func runTests(t *testing.T, testCases []*testCase, prefix string, lspFunc func(h *lntest.TestHarness, m *lntest.Miner) LspNode) { for _, testCase := range testCases { testCase := testCase t.Run(fmt.Sprintf("%s: %s", prefix, testCase.name), func(t *testing.T) { @@ -31,30 +31,31 @@ func runTests(t *testing.T, testCases []*testCase, prefix string, lspFunc func(h } } -func runTest(t *testing.T, testCase *testCase, prefix string, lspFunc func(h *lntest.TestHarness, m *lntest.Miner, t time.Time) LspNode) { +func runTest(t *testing.T, testCase *testCase, prefix string, lspFunc func(h *lntest.TestHarness, m *lntest.Miner) LspNode) { log.Printf("%s: Running test case '%s'", prefix, testCase.name) - harness := lntest.NewTestHarness(t) - defer harness.TearDown() - var dd time.Duration to := testCase.timeout if to == dd { to = defaultTimeout } - timeout := time.Now().Add(to) - log.Printf("Using timeout %v", timeout.String()) + deadline := time.Now().Add(to) + log.Printf("Using deadline %v", deadline.String()) + + harness := lntest.NewTestHarness(t, deadline) + defer harness.TearDown() + log.Printf("Creating miner") miner := lntest.NewMiner(harness) log.Printf("Creating lsp") - lsp := lspFunc(harness, miner, timeout) + lsp := lspFunc(harness, miner) log.Printf("Run testcase") - testCase.test(harness, lsp, miner, timeout) + testCase.test(harness, lsp, miner) } type testCase struct { name string - test func(h *lntest.TestHarness, lsp LspNode, miner *lntest.Miner, timeout time.Time) + test func(h *lntest.TestHarness, lsp LspNode, miner *lntest.Miner) timeout time.Duration } diff --git a/itest/zero_conf_node.go b/itest/zero_conf_node.go index e90c7f6..94e1ec0 100644 --- a/itest/zero_conf_node.go +++ b/itest/zero_conf_node.go @@ -6,7 +6,6 @@ import ( "fmt" "os" "path/filepath" - "time" "github.com/breez/lntest" btcec "github.com/btcsuite/btcd/btcec/v2" @@ -51,7 +50,7 @@ pip install pyln-client > /dev/null 2>&1 python %s ` -func NewZeroConfNode(h *lntest.TestHarness, m *lntest.Miner, name string, timeout time.Time) *ZeroConfNode { +func NewZeroConfNode(h *lntest.TestHarness, m *lntest.Miner, name string) *ZeroConfNode { privKey, err := btcec.NewPrivateKey() lntest.CheckError(h.T, err) @@ -89,7 +88,6 @@ func NewZeroConfNode(h *lntest.TestHarness, m *lntest.Miner, name string, timeou h, m, name, - timeout, fmt.Sprintf("--dev-force-privkey=%x", s), fmt.Sprintf("--plugin=%s", pluginFilePath), ) From 9040351c8cb431ea3bacd76b8db6179bf8a34b0a Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Wed, 7 Dec 2022 14:36:04 +0100 Subject: [PATCH 099/214] use zero reserve for all channel opens with cln --- cln_client.go | 1 + go.mod | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/cln_client.go b/cln_client.go index 7a47395..56ac320 100644 --- a/cln_client.go +++ b/cln_client.go @@ -86,6 +86,7 @@ func (c *ClnClient) OpenChannel(req *OpenChannelRequest) (*wire.OutPoint, error) &minConf, glightning.NewMsat(0), minDepth, + glightning.NewSat(0), ) if err != nil { diff --git a/go.mod b/go.mod index d4b13f6..6c275af 100644 --- a/go.mod +++ b/go.mod @@ -176,4 +176,4 @@ require ( replace github.com/lightningnetwork/lnd v0.15.1-beta => github.com/breez/lnd v0.15.0-beta.rc6.0.20220831104847-00b86a81e57a -replace github.com/niftynei/glightning v0.8.2 => github.com/breez/glightning v0.0.0-20221207122347-03c2d8cb69dd \ No newline at end of file +replace github.com/niftynei/glightning v0.8.2 => github.com/breez/glightning v0.0.0-20221207132824-fb0b6f4f7483 From 7c803d12bf5b61a4692cd880cf9313dd12c70345 Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Sat, 10 Dec 2022 11:40:03 +0100 Subject: [PATCH 100/214] add a test for zero reserve --- go.mod | 10 +++--- itest/intercept_zero_conf_test.go | 18 ++++++++-- itest/lspd_node.go | 2 ++ itest/lspd_test.go | 4 +++ itest/test_common.go | 15 +++++++- itest/zero_conf_node.go | 7 ++++ itest/zero_reserve_test.go | 60 +++++++++++++++++++++++++++++++ 7 files changed, 106 insertions(+), 10 deletions(-) create mode 100644 itest/zero_reserve_test.go diff --git a/go.mod b/go.mod index 6c275af..fad622f 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.19 require ( github.com/aws/aws-sdk-go v1.30.20 - github.com/breez/lntest v0.0.7 + github.com/breez/lntest v0.0.8 github.com/btcsuite/btcd v0.23.1 github.com/btcsuite/btcd/btcec/v2 v2.2.1 github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 @@ -18,17 +18,18 @@ require ( github.com/jackc/pgx/v4 v4.13.0 github.com/lightningnetwork/lightning-onion v1.2.0 github.com/lightningnetwork/lnd v0.15.1-beta + github.com/lightningnetwork/lnd/tlv v1.0.3 github.com/niftynei/glightning v0.8.2 + github.com/stretchr/testify v1.8.1 + golang.org/x/exp v0.0.0-20221114191408-850992195362 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c google.golang.org/grpc v1.50.1 - gotest.tools v2.2.0+incompatible ) require ( github.com/Microsoft/go-winio v0.6.0 // indirect github.com/docker/distribution v2.8.1+incompatible // indirect github.com/docker/go-units v0.5.0 // indirect - github.com/google/go-cmp v0.5.8 // indirect github.com/moby/term v0.0.0-20221120202655-abb19827d345 // indirect github.com/morikuni/aec v1.0.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect @@ -107,7 +108,6 @@ require ( github.com/lightningnetwork/lnd/kvdb v1.3.1 // indirect github.com/lightningnetwork/lnd/queue v1.1.0 // indirect github.com/lightningnetwork/lnd/ticker v1.1.0 // indirect - github.com/lightningnetwork/lnd/tlv v1.0.3 github.com/lightningnetwork/lnd/tor v1.0.1 // indirect github.com/ltcsuite/ltcd v0.0.0-20190101042124-f37f8bf35796 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect @@ -128,7 +128,6 @@ require ( github.com/soheilhy/cmux v0.1.5 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/stretchr/objx v0.5.0 // indirect - github.com/stretchr/testify v1.8.1 // indirect github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802 // indirect github.com/ulikunitz/xz v0.5.10 // indirect @@ -156,7 +155,6 @@ require ( go.uber.org/multierr v1.8.0 // indirect go.uber.org/zap v1.17.0 // indirect golang.org/x/crypto v0.1.0 // indirect - golang.org/x/exp v0.0.0-20221114191408-850992195362 golang.org/x/net v0.1.0 // indirect golang.org/x/sys v0.1.0 // indirect golang.org/x/term v0.1.0 // indirect diff --git a/itest/intercept_zero_conf_test.go b/itest/intercept_zero_conf_test.go index fd40f78..e0e64a9 100644 --- a/itest/intercept_zero_conf_test.go +++ b/itest/intercept_zero_conf_test.go @@ -5,7 +5,7 @@ import ( "github.com/breez/lntest" lspd "github.com/breez/lspd/rpc" - "gotest.tools/assert" + "github.com/stretchr/testify/assert" ) func testOpenZeroConfChannelOnReceive(h *lntest.TestHarness, lsp LspNode, miner *lntest.Miner) { @@ -50,8 +50,14 @@ func testOpenZeroConfChannelOnReceive(h *lntest.TestHarness, lsp LspNode, miner payResp := alice.Pay(outerInvoice.bolt11) bobInvoice := bob.lightningNode.GetInvoice(payResp.PaymentHash) - assert.DeepEqual(h.T, payResp.PaymentPreimage, bobInvoice.PaymentPreimage) + assert.Equal(h.T, payResp.PaymentPreimage, bobInvoice.PaymentPreimage) assert.Equal(h.T, outerAmountMsat, bobInvoice.AmountReceivedMsat) + + // Make sure capacity is correct + chans := bob.lightningNode.GetChannels() + assert.Len(h.T, chans, 1) + c := chans[0] + AssertChannelCapacity(h.T, outerAmountMsat, c.CapacityMsat) } func testOpenZeroConfSingleHtlc(h *lntest.TestHarness, lsp LspNode, miner *lntest.Miner) { @@ -97,8 +103,14 @@ func testOpenZeroConfSingleHtlc(h *lntest.TestHarness, lsp LspNode, miner *lntes payResp := alice.PayViaRoute(outerAmountMsat, outerInvoice.paymentHash, outerInvoice.paymentSecret, route) bobInvoice := bob.lightningNode.GetInvoice(payResp.PaymentHash) - assert.DeepEqual(h.T, payResp.PaymentPreimage, bobInvoice.PaymentPreimage) + assert.Equal(h.T, payResp.PaymentPreimage, bobInvoice.PaymentPreimage) assert.Equal(h.T, outerAmountMsat, bobInvoice.AmountReceivedMsat) + + // Make sure capacity is correct + chans := bob.lightningNode.GetChannels() + assert.Len(h.T, chans, 1) + c := chans[0] + AssertChannelCapacity(h.T, outerAmountMsat, c.CapacityMsat) } func constructRoute( diff --git a/itest/lspd_node.go b/itest/lspd_node.go index 012354c..52ca89c 100644 --- a/itest/lspd_node.go +++ b/itest/lspd_node.go @@ -141,6 +141,8 @@ func NewClnLspdNode(h *lntest.TestHarness, m *lntest.Miner, name string) LspNode fmt.Sprintf("--fee-base=%d", lspBaseFeeMsat), fmt.Sprintf("--fee-per-satoshi=%d", lspFeeRatePpm), fmt.Sprintf("--cltv-delta=%d", lspCltvDelta), + "--max-concurrent-htlcs=30", + "--dev-allowdustreserve=true", } lightningNode := lntest.NewCoreLightningNode(h, m, name, args...) diff --git a/itest/lspd_test.go b/itest/lspd_test.go index 1e5b092..366f953 100644 --- a/itest/lspd_test.go +++ b/itest/lspd_test.go @@ -68,4 +68,8 @@ var allTestCases = []*testCase{ name: "testOpenZeroConfSingleHtlc", test: testOpenZeroConfSingleHtlc, }, + { + name: "testZeroReserve", + test: testZeroReserve, + }, } diff --git a/itest/test_common.go b/itest/test_common.go index 50600c7..926d673 100644 --- a/itest/test_common.go +++ b/itest/test_common.go @@ -1,6 +1,11 @@ package itest -import "crypto/rand" +import ( + "crypto/rand" + "testing" + + "github.com/stretchr/testify/assert" +) func GenerateRandomBytes(n int) ([]byte, error) { b := make([]byte, n) @@ -12,3 +17,11 @@ func GenerateRandomBytes(n int) ([]byte, error) { return b, nil } + +func AssertChannelCapacity( + t *testing.T, + outerAmountMsat uint64, + capacityMsat uint64, +) { + assert.Equal(t, ((outerAmountMsat/1000)+100000)*1000, capacityMsat) +} diff --git a/itest/zero_conf_node.go b/itest/zero_conf_node.go index 94e1ec0..4b472f1 100644 --- a/itest/zero_conf_node.go +++ b/itest/zero_conf_node.go @@ -90,6 +90,13 @@ func NewZeroConfNode(h *lntest.TestHarness, m *lntest.Miner, name string) *ZeroC name, fmt.Sprintf("--dev-force-privkey=%x", s), fmt.Sprintf("--plugin=%s", pluginFilePath), + // NOTE: max-concurrent-htlcs is 30 on mainnet by default. In cln V22.11 + // there is a check for 'all dust' commitment transactions. The max + // concurrent HTLCs of both sides of the channel * dust limit must be + // lower than the channel capacity in order to open a zero conf zero + // reserve channel. Relevant code: + // https://github.com/ElementsProject/lightning/blob/774d16a72e125e4ae4e312b9e3307261983bec0e/openingd/openingd.c#L481-L520 + "--max-concurrent-htlcs=30", ) return &ZeroConfNode{ diff --git a/itest/zero_reserve_test.go b/itest/zero_reserve_test.go new file mode 100644 index 0000000..36aa9d5 --- /dev/null +++ b/itest/zero_reserve_test.go @@ -0,0 +1,60 @@ +package itest + +import ( + "log" + + "github.com/breez/lntest" + lspd "github.com/breez/lspd/rpc" + "github.com/stretchr/testify/assert" +) + +func testZeroReserve(h *lntest.TestHarness, lsp LspNode, miner *lntest.Miner) { + alice := lntest.NewCoreLightningNode(h, miner, "Alice") + bob := NewZeroConfNode(h, miner, "Bob") + + alice.Fund(10000000) + lsp.LightningNode().Fund(10000000) + + log.Print("Opening channel between Alice and the lsp") + channel := alice.OpenChannel(lsp.LightningNode(), &lntest.OpenChannelOptions{ + AmountSat: 1000000, + }) + channelId := alice.WaitForChannelReady(channel) + + log.Printf("Adding bob's invoices") + outerAmountMsat := uint64(2100000) + innerAmountMsat := uint64(2100000) + description := "Please pay me" + innerInvoice, outerInvoice := bob.GenerateInvoices(generateInvoicesRequest{ + innerAmountMsat: innerAmountMsat, + outerAmountMsat: outerAmountMsat, + description: description, + lsp: lsp, + }) + + log.Print("Connecting bob to lspd") + bob.lightningNode.ConnectPeer(lsp.LightningNode()) + + // NOTE: We pretend to be paying fees to the lsp, but actually we won't. + log.Printf("Registering payment with lsp") + pretendAmount := outerAmountMsat - 2000000 + RegisterPayment(lsp, &lspd.PaymentInformation{ + PaymentHash: innerInvoice.paymentHash, + PaymentSecret: innerInvoice.paymentSecret, + Destination: bob.lightningNode.NodeId(), + IncomingAmountMsat: int64(outerAmountMsat), + OutgoingAmountMsat: int64(pretendAmount), + }) + + log.Printf("Alice paying") + route := constructRoute(lsp.LightningNode(), bob.lightningNode, channelId, lntest.NewShortChanIDFromString("1x0x0"), outerAmountMsat) + alice.PayViaRoute(outerAmountMsat, outerInvoice.paymentHash, outerInvoice.paymentSecret, route) + + // Make sure balance is correct + chans := bob.lightningNode.GetChannels() + assert.Len(h.T, chans, 1) + + c := chans[0] + assert.Equal(h.T, c.RemoteReserveMsat, c.CapacityMsat/100) + assert.Zero(h.T, c.LocalReserveMsat) +} From ec3b4d2f0c8de8da138fb080c1124a02b1457b63 Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Sat, 10 Dec 2022 13:57:26 +0100 Subject: [PATCH 101/214] update docs with cln and integration tests --- README.md | 62 +++++++++++++++++++++++++++++++++++++++++++++++------- sample.env | 15 ++++++++----- 2 files changed, 64 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 13dd83b..f372f75 100644 --- a/README.md +++ b/README.md @@ -1,26 +1,52 @@ # lspd simple server lspd is a simple deamon that provides [LSP](https://medium.com/breez-technology/introducing-lightning-service-providers-fe9fb1665d5f) services to [Breez clients](https://github.com/breez/breezmobile). -This is a simple example of an lspd that works with an [lnd](https://github.com/lightningnetwork/lnd) node. +This is a simple example of an lspd that works with an [lnd](https://github.com/lightningnetwork/lnd) node or a [cln](https://github.com/ElementsProject/lightning) node. ## Installation +### Build 1. git clone https://github.com/breez/lspd (or fork) 1. Compile lspd using `go build .` + +### Before running 1. Create a random token (for instance using the command `openssl rand -base64 48`) 1. Define the environment variables as described in sample.env. If `CERTMAGIC_DOMAIN` is defined, certificate for this domain is automatically obtained and renewed from Let's Encrypt. In this case, the port needs to be 443. If `CERTMAGIC_DOMAIN` is not defined, lspd needs to run behind a reverse proxy like treafik or nginx. + +### Running lspd on LND +1. Run LND with the following options set: + - `--protocol.zero-conf`: for being able to open zero conf channels + - `--protocol.option-scid-alias`: required for zero conf channels + - `--requireinterceptor`: to make sure all htlcs are intercepted by lspd + - `--bitcoin.chanreservescript="0"` to allow the client to have zero reserve on their side 1. Run lspd + +### Running lspd on CLN +lspd on core lightning is run as a plugin. In order to load the environment veriables in the plugin runtime, they either have to be set on the machine, or the plugin should be started with a shell script: + +```bash +#!/bin/bash + +export TOKEN= +export LSPD_PRIVATE_KEY= +export DATABASE_URL= +export RUN_CLN=true + +# Etc. for all env variables in sample.env + +/path/to/lspd +``` + +Run cln with the following options set: +- `--plugin=/path/to/shell/script.sh`: to use lspd as plugin +- `--max-concurrent-htlcs=30`: In order to use zero reserve channels on the client side, (local max_accepted_htlcs + remote max_accepted_htlcs + 2) * dust limit must be lower than the channel capacity. Reduce max-concurrent-htlcs or increase channel capacity accordingly. +- `--dev-allowdustreserve=true`: In order to allow zero reserve on the client side, you'll need to enable developer mode on cln (`./configure --enable-developer`) + +### Final step 1. Share with Breez the TOKEN and the LISTEN_ADDRESS you've defined (send to contact@breez.technology) ## Implement your own lspd You can create your own lsdp by implementing the grpc methods described [here](https://github.com/breez/lspd/blob/master/rpc/lspd.md). -## Use a smaller channel reserve -You can apply the PR from https://github.com/lightningnetwork/lnd/pull/2708 to be able to create channels with a channel reserve smaller than 1% of the channel capacity. -Then add the field `RemoteChanReserveSat` in the `lnrpc.OpenChannelRequest` struct when opening a channel. - -In order to be able to let clients have a zero channel reserve, you can apply the -commit from https://github.com/breez/lnd/commit/03a7a0b6b4c8fa92ad94e9f449135e0738702643 - ## Flow for creating channels When Alice wants Bob to pay her an amount and Alice doesn't have a channel with sufficient capacity, she calls the lspd function RegisterPayment() and sending the paymentHash, paymentSecret (for mpp payments), destination (Alice pubkey), and two amounts. The first amount (incoming from the lsp point of view) is the amount BOB will pay. The second amount (outgoing from the lsp point of view) is the amount Alice will receive. The difference between these two amounts is the fees for the lsp. @@ -28,3 +54,23 @@ In order to open the channel on the fly, the lsp is connecting to lnd using the ## Probing support The lsp supports probing non-mpp payments if the payment hash for probing is sha256('probing-01:' || payment_hash) when payment_hash is the hash of the real payment. + +## Integration tests +In order to run the integration tests, you need: +- Docker running +- python3 installed +- A development build of lightningd v22.11 +- lnd v0.15.4 +- bitcoind v23.0 +- bitcoin-cli v23.0 +- build of lspd + +To run the integration tests, run the following command from the lspd root directory (replacing the appropriate paths). + +``` +go test -v ./itest --lightningdexec /full/path/to/lightningd --lndexec /full/path/to/lnd --bitcoindexec /full/path/to/bitcoind --bitcoincliexec /full/path/to/bitcoin-cli --lspdexec /full/path/to/lspd --lspdmigrationsdir /full/path/to/lspd/postgresql/migrations --testdir /path/to/test/output/dir +``` +- Optional: `--preservelogs` persists only the logs in the testing directory +- Optional: `--preservestate` preserves all artifacts from the lightning nodes, miners, postgres container and startup scripts + +Alternatively, if `lightningd`, `lnd`, `bitcoind`, `bitcoin-cli` or `lspd` exec flags are omitted, these binaries are taken from `$PATH` instead if found. \ No newline at end of file diff --git a/sample.env b/sample.env index 880c770..901d9e0 100644 --- a/sample.env +++ b/sample.env @@ -3,10 +3,6 @@ LISTEN_ADDRESS= ### a certificate from Let's Encrypt #CERTMAGIC_DOMAIN= -LND_ADDRESS= -LND_CERT= #replace each eol by \\n -LND_MACAROON_HEX= - NODE_NAME= NODE_PUBKEY= NODE_HOST= @@ -25,4 +21,13 @@ OPENCHANNEL_NOTIFICATION_FROM="Name4 " CHANNELMISMATCH_NOTIFICATION_TO='["Name1 "]' CHANNELMISMATCH_NOTIFICATION_CC='["Name2 ","Name3 "]' -CHANNELMISMATCH_NOTIFICATION_FROM="Name4 " \ No newline at end of file +CHANNELMISMATCH_NOTIFICATION_FROM="Name4 " + +# LND specific environment variables +LND_ADDRESS= +LND_CERT= #replace each eol by \\n +LND_MACAROON_HEX= +RUN_LND=true + +# CLN specific environment variables +RUN_CLN=true From 8048dae0c5ffe10e4575daa7728e01017bb8e7d3 Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Thu, 15 Dec 2022 15:04:41 +0100 Subject: [PATCH 102/214] attempt ecies for decryption --- go.mod | 28 +++++++++++++++------------- server.go | 29 +++++++++++++++++++++-------- 2 files changed, 36 insertions(+), 21 deletions(-) diff --git a/go.mod b/go.mod index fad622f..dabae4d 100644 --- a/go.mod +++ b/go.mod @@ -4,8 +4,8 @@ go 1.19 require ( github.com/aws/aws-sdk-go v1.30.20 - github.com/breez/lntest v0.0.8 - github.com/btcsuite/btcd v0.23.1 + github.com/breez/lntest v0.0.9 + github.com/btcsuite/btcd v0.23.3 github.com/btcsuite/btcd/btcec/v2 v2.2.1 github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 github.com/caddyserver/certmagic v0.11.2 @@ -17,26 +17,28 @@ require ( github.com/jackc/pgtype v1.8.1 github.com/jackc/pgx/v4 v4.13.0 github.com/lightningnetwork/lightning-onion v1.2.0 - github.com/lightningnetwork/lnd v0.15.1-beta + github.com/lightningnetwork/lnd v0.15.4-beta github.com/lightningnetwork/lnd/tlv v1.0.3 github.com/niftynei/glightning v0.8.2 github.com/stretchr/testify v1.8.1 - golang.org/x/exp v0.0.0-20221114191408-850992195362 + golang.org/x/exp v0.0.0-20221212164502-fae10dda9338 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c google.golang.org/grpc v1.50.1 ) require ( - github.com/Microsoft/go-winio v0.6.0 // indirect + github.com/Microsoft/go-winio v0.5.2 // indirect + github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344 // indirect github.com/docker/distribution v2.8.1+incompatible // indirect github.com/docker/go-units v0.5.0 // indirect + github.com/ethereum/go-ethereum v1.10.17 // indirect github.com/moby/term v0.0.0-20221120202655-abb19827d345 // indirect github.com/morikuni/aec v1.0.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.0.2 // indirect github.com/pkg/errors v0.9.1 // indirect - golang.org/x/mod v0.6.0 // indirect - golang.org/x/tools v0.2.0 // indirect + gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec // indirect + golang.org/x/net v0.1.0 // indirect gotest.tools/v3 v3.4.0 // indirect ) @@ -48,10 +50,10 @@ require ( github.com/btcsuite/btcd/btcutil v1.1.2 // indirect github.com/btcsuite/btcd/btcutil/psbt v1.1.5 // indirect github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f // indirect - github.com/btcsuite/btcwallet v0.15.1 // indirect - github.com/btcsuite/btcwallet/wallet/txauthor v1.2.3 // indirect + github.com/btcsuite/btcwallet v0.16.1 // indirect + github.com/btcsuite/btcwallet/wallet/txauthor v1.3.2 // indirect github.com/btcsuite/btcwallet/wallet/txrules v1.2.0 // indirect - github.com/btcsuite/btcwallet/wallet/txsizes v1.1.0 // indirect + github.com/btcsuite/btcwallet/wallet/txsizes v1.2.3 // indirect github.com/btcsuite/btcwallet/walletdb v1.4.0 // indirect github.com/btcsuite/btcwallet/wtxmgr v1.5.0 // indirect github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd // indirect @@ -67,6 +69,7 @@ require ( github.com/dsnet/compress v0.0.1 // indirect github.com/dustin/go-humanize v1.0.0 // indirect github.com/dvyukov/go-fuzz v0.0.0-20210602112143-b1f3d6f4ef4e // indirect + github.com/ecies/go/v2 v2.0.4 github.com/fergusstrange/embedded-postgres v1.10.0 // indirect github.com/form3tech-oss/jwt-go v3.2.3+incompatible // indirect github.com/go-acme/lego/v3 v3.7.0 // indirect @@ -87,7 +90,7 @@ require ( github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect github.com/jackc/puddle v1.1.3 // indirect github.com/jessevdk/go-flags v1.4.0 // indirect - github.com/jmespath/go-jmespath v0.3.0 // indirect + github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/jonboulle/clockwork v0.2.2 // indirect github.com/jrick/logrotate v1.0.0 // indirect github.com/json-iterator/go v1.1.11 // indirect @@ -155,7 +158,6 @@ require ( go.uber.org/multierr v1.8.0 // indirect go.uber.org/zap v1.17.0 // indirect golang.org/x/crypto v0.1.0 // indirect - golang.org/x/net v0.1.0 // indirect golang.org/x/sys v0.1.0 // indirect golang.org/x/term v0.1.0 // indirect golang.org/x/text v0.4.0 // indirect @@ -172,6 +174,6 @@ require ( sigs.k8s.io/yaml v1.2.0 // indirect ) -replace github.com/lightningnetwork/lnd v0.15.1-beta => github.com/breez/lnd v0.15.0-beta.rc6.0.20220831104847-00b86a81e57a +replace github.com/lightningnetwork/lnd v0.15.4-beta => github.com/breez/lnd v0.15.0-beta.rc6.0.20220831104847-00b86a81e57a replace github.com/niftynei/glightning v0.8.2 => github.com/breez/glightning v0.0.0-20221207132824-fb0b6f4f7483 diff --git a/server.go b/server.go index 1d1ad8c..125ed9c 100644 --- a/server.go +++ b/server.go @@ -12,6 +12,7 @@ import ( "github.com/breez/lspd/btceclegacy" lspdrpc "github.com/breez/lspd/rpc" + ecies "github.com/ecies/go/v2" "github.com/golang/protobuf/proto" grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware" "google.golang.org/grpc" @@ -49,6 +50,7 @@ var ( client LightningClient openChannelReqGroup singleflight.Group privateKey *btcec.PrivateKey + privateKeyBytes []byte publicKey *btcec.PublicKey nodeName = os.Getenv("NODE_NAME") nodePubkey = os.Getenv("NODE_PUBKEY") @@ -73,11 +75,16 @@ func (s *server) ChannelInformation(ctx context.Context, in *lspdrpc.ChannelInfo } func (s *server) RegisterPayment(ctx context.Context, in *lspdrpc.RegisterPaymentRequest) (*lspdrpc.RegisterPaymentReply, error) { - data, err := btceclegacy.Decrypt(privateKey, in.Blob) + data, err := ecies.Decrypt(ecies.NewPrivateKeyFromBytes(privateKeyBytes), 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) + log.Printf("ecies.Decrypt(%x) error: %v", in.Blob, err) + 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 { @@ -150,10 +157,14 @@ func (s *server) OpenChannel(ctx context.Context, in *lspdrpc.OpenChannelRequest } func getSignedEncryptedData(in *lspdrpc.Encrypted) (string, []byte, error) { - signedBlob, err := btceclegacy.Decrypt(privateKey, in.Data) + signedBlob, err := ecies.Decrypt(ecies.NewPrivateKeyFromBytes(privateKeyBytes), 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) + log.Printf("ecies.Decrypt(%x) error: %v", in.Data, err) + 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) @@ -249,11 +260,13 @@ func NewGrpcServer() *server { } func (s *server) Start() error { - privateKeyBytes, err := hex.DecodeString(os.Getenv("LSPD_PRIVATE_KEY")) + pk, 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) + + privateKeyBytes = pk + privateKey, publicKey = btcec.PrivKeyFromBytes(pk) certmagicDomain := os.Getenv("CERTMAGIC_DOMAIN") address := os.Getenv("LISTEN_ADDRESS") From 5cee5f3ec9989502a5f6ae668f07b538accfee7d Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Thu, 15 Dec 2022 15:15:10 +0100 Subject: [PATCH 103/214] also encrypt using ecies --- server.go | 49 ++++++++++++++++++++++++++++++++----------------- 1 file changed, 32 insertions(+), 17 deletions(-) diff --git a/server.go b/server.go index 125ed9c..03aaba5 100644 --- a/server.go +++ b/server.go @@ -50,8 +50,9 @@ var ( client LightningClient openChannelReqGroup singleflight.Group privateKey *btcec.PrivateKey - privateKeyBytes []byte publicKey *btcec.PublicKey + eciesPrivateKey *ecies.PrivateKey + eciesPublicKey *ecies.PublicKey nodeName = os.Getenv("NODE_NAME") nodePubkey = os.Getenv("NODE_PUBKEY") ) @@ -75,7 +76,7 @@ func (s *server) ChannelInformation(ctx context.Context, in *lspdrpc.ChannelInfo } func (s *server) RegisterPayment(ctx context.Context, in *lspdrpc.RegisterPaymentRequest) (*lspdrpc.RegisterPaymentReply, error) { - data, err := ecies.Decrypt(ecies.NewPrivateKeyFromBytes(privateKeyBytes), in.Blob) + data, err := ecies.Decrypt(eciesPrivateKey, in.Blob) if err != nil { log.Printf("ecies.Decrypt(%x) error: %v", in.Blob, err) data, err = btceclegacy.Decrypt(privateKey, in.Blob) @@ -156,46 +157,48 @@ func (s *server) OpenChannel(ctx context.Context, in *lspdrpc.OpenChannelRequest return r.(*lspdrpc.OpenChannelReply), err } -func getSignedEncryptedData(in *lspdrpc.Encrypted) (string, []byte, error) { - signedBlob, err := ecies.Decrypt(ecies.NewPrivateKeyFromBytes(privateKeyBytes), in.Data) +func getSignedEncryptedData(in *lspdrpc.Encrypted) (string, []byte, bool, error) { + usedEcies := true + signedBlob, err := ecies.Decrypt(eciesPrivateKey, in.Data) if err != nil { log.Printf("ecies.Decrypt(%x) error: %v", in.Data, err) + usedEcies = false 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) + return "", nil, usedEcies, 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) + return "", nil, usedEcies, 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) + return "", nil, usedEcies, 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) + return "", nil, usedEcies, 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", + return "", nil, usedEcies, 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 "", nil, usedEcies, fmt.Errorf("invalid signature") } - return hex.EncodeToString(signed.Pubkey), signed.Data, nil + return hex.EncodeToString(signed.Pubkey), signed.Data, usedEcies, nil } func (s *server) CheckChannels(ctx context.Context, in *lspdrpc.Encrypted) (*lspdrpc.Encrypted, error) { - nodeID, data, err := getSignedEncryptedData(in) + nodeID, data, usedEcies, err := getSignedEncryptedData(in) if err != nil { log.Printf("getSignedEncryptedData error: %v", err) return nil, fmt.Errorf("getSignedEncryptedData error: %v", err) @@ -230,11 +233,22 @@ func (s *server) CheckChannels(ctx context.Context, in *lspdrpc.Encrypted) (*lsp 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) + + var encrypted []byte + if usedEcies { + encrypted, err = ecies.Encrypt(eciesPublicKey, dataReply) + if err != nil { + log.Printf("ecies.Encrypt() error: %v", err) + return nil, fmt.Errorf("ecies.Encrypt() error: %w", err) + } + } else { + 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 } @@ -265,7 +279,8 @@ func (s *server) Start() error { log.Fatalf("hex.DecodeString(os.Getenv(\"LSPD_PRIVATE_KEY\")=%v) error: %v", os.Getenv("LSPD_PRIVATE_KEY"), err) } - privateKeyBytes = pk + eciesPrivateKey = ecies.NewPrivateKeyFromBytes(pk) + eciesPublicKey = eciesPrivateKey.PublicKey privateKey, publicKey = btcec.PrivKeyFromBytes(pk) certmagicDomain := os.Getenv("CERTMAGIC_DOMAIN") From 18af318798fcc2e88dd266b60992da1eafa3e1ae Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Thu, 15 Dec 2022 15:25:45 +0100 Subject: [PATCH 104/214] use ecies for registerpayment in itest --- itest/lspd_node.go | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/itest/lspd_node.go b/itest/lspd_node.go index 52ca89c..c5dd445 100644 --- a/itest/lspd_node.go +++ b/itest/lspd_node.go @@ -12,10 +12,10 @@ import ( "strings" "github.com/breez/lntest" - "github.com/breez/lspd/btceclegacy" lspd "github.com/breez/lspd/rpc" "github.com/btcsuite/btcd/btcec/v2" "github.com/decred/dcrd/dcrec/secp256k1/v4" + ecies "github.com/ecies/go/v2" "github.com/golang/protobuf/proto" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" @@ -39,6 +39,7 @@ var ( type LspNode interface { Harness() *lntest.TestHarness PublicKey() *btcec.PublicKey + EciesPublicKey() *ecies.PublicKey Rpc() lspd.ChannelOpenerClient NodeId() []byte LightningNode() lntest.LightningNode @@ -49,6 +50,7 @@ type ClnLspNode struct { lightningNode *lntest.CoreLightningNode rpc lspd.ChannelOpenerClient publicKey btcec.PublicKey + eciesPublicKey ecies.PublicKey postgresBackend *PostgresContainer } @@ -60,6 +62,10 @@ func (c *ClnLspNode) PublicKey() *btcec.PublicKey { return &c.publicKey } +func (c *ClnLspNode) EciesPublicKey() *ecies.PublicKey { + return &c.eciesPublicKey +} + func (c *ClnLspNode) Rpc() lspd.ChannelOpenerClient { return c.rpc } @@ -86,6 +92,7 @@ type LndLspNode struct { lightningNode *lntest.LndNode rpc lspd.ChannelOpenerClient publicKey btcec.PublicKey + eciesPublicKey ecies.PublicKey postgresBackend *PostgresContainer logFile *os.File lspdCmd *exec.Cmd @@ -99,6 +106,10 @@ func (c *LndLspNode) PublicKey() *btcec.PublicKey { return &c.publicKey } +func (c *LndLspNode) EciesPublicKey() *ecies.PublicKey { + return &c.eciesPublicKey +} + func (c *LndLspNode) Rpc() lspd.ChannelOpenerClient { return c.rpc } @@ -135,7 +146,7 @@ func (l *LndLspNode) LightningNode() lntest.LightningNode { } func NewClnLspdNode(h *lntest.TestHarness, m *lntest.Miner, name string) LspNode { - scriptFilePath, grpcAddress, publ, postgresBackend := setupLspd(h, name, "RUN_CLN=true") + scriptFilePath, grpcAddress, publ, eciesPubl, postgresBackend := setupLspd(h, name, "RUN_CLN=true") args := []string{ fmt.Sprintf("--plugin=%s", scriptFilePath), fmt.Sprintf("--fee-base=%d", lspBaseFeeMsat), @@ -161,6 +172,7 @@ func NewClnLspdNode(h *lntest.TestHarness, m *lntest.Miner, name string) LspNode lightningNode: lightningNode, rpc: client, publicKey: *publ, + eciesPublicKey: *eciesPubl, postgresBackend: postgresBackend, } @@ -183,7 +195,7 @@ func NewLndLspdNode(h *lntest.TestHarness, m *lntest.Miner, name string) LspNode lightningNode := lntest.NewLndNode(h, m, name, args...) tlsCert := strings.Replace(string(lightningNode.TlsCert()), "\n", "\\n", -1) - scriptFilePath, grpcAddress, publ, postgresBackend := setupLspd(h, name, + scriptFilePath, grpcAddress, publ, eciesPubl, postgresBackend := setupLspd(h, name, "RUN_LND=true", fmt.Sprintf("LND_CERT=\"%s\"", tlsCert), fmt.Sprintf("LND_ADDRESS=%s", lightningNode.GrpcHost()), @@ -218,6 +230,7 @@ func NewLndLspdNode(h *lntest.TestHarness, m *lntest.Miner, name string) LspNode lightningNode: lightningNode, rpc: client, publicKey: *publ, + eciesPublicKey: *eciesPubl, postgresBackend: postgresBackend, logFile: logFile, lspdCmd: lspdCmd, @@ -228,7 +241,7 @@ func NewLndLspdNode(h *lntest.TestHarness, m *lntest.Miner, name string) LspNode return lspNode } -func setupLspd(h *lntest.TestHarness, name string, envExt ...string) (string, string, *secp256k1.PublicKey, *PostgresContainer) { +func setupLspd(h *lntest.TestHarness, name string, envExt ...string) (string, string, *secp256k1.PublicKey, *ecies.PublicKey, *PostgresContainer) { scriptDir := h.GetDirectory(fmt.Sprintf("lspd-%s", name)) log.Printf("%s: Creating LSPD in dir %s", name, scriptDir) migrationsDir, err := getMigrationsDir() @@ -249,6 +262,7 @@ func setupLspd(h *lntest.TestHarness, name string, envExt ...string) (string, st lntest.CheckError(h.T, err) _, publ := btcec.PrivKeyFromBytes(lspdPrivateKeyBytes) + eciesPubl := ecies.NewPrivateKeyFromBytes(lspdPrivateKeyBytes).PublicKey host := "localhost" grpcAddress := fmt.Sprintf("%s:%d", host, lspdPort) env := []string{ @@ -284,14 +298,14 @@ func setupLspd(h *lntest.TestHarness, name string, envExt ...string) (string, st lntest.CheckError(h.T, err) scriptFile.Close() - return scriptFilePath, grpcAddress, publ, postgresBackend + return scriptFilePath, grpcAddress, publ, eciesPubl, postgresBackend } func RegisterPayment(l LspNode, paymentInfo *lspd.PaymentInformation) { serialized, err := proto.Marshal(paymentInfo) lntest.CheckError(l.Harness().T, err) - encrypted, err := btceclegacy.Encrypt(l.PublicKey(), serialized) + encrypted, err := ecies.Encrypt(l.EciesPublicKey(), serialized) lntest.CheckError(l.Harness().T, err) log.Printf("Registering payment") From 336297da7b09e282a5f67bca24c2364e0a237f26 Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Sat, 10 Dec 2022 19:37:43 +0100 Subject: [PATCH 105/214] shared testparams --- go.mod | 2 + itest/{zero_conf_node.go => breez_client.go} | 32 +++++----- itest/intercept_zero_conf_test.go | 65 ++++++++++---------- itest/lspd_test.go | 32 ++++++---- itest/test_params.go | 35 +++++++++++ itest/zero_reserve_test.go | 30 +++++---- 6 files changed, 117 insertions(+), 79 deletions(-) rename itest/{zero_conf_node.go => breez_client.go} (87%) create mode 100644 itest/test_params.go diff --git a/go.mod b/go.mod index dabae4d..bdd0d90 100644 --- a/go.mod +++ b/go.mod @@ -43,6 +43,7 @@ require ( ) require ( + github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344 // indirect github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect github.com/aead/siphash v1.0.1 // indirect github.com/andybalholm/brotli v1.0.3 // indirect @@ -136,6 +137,7 @@ require ( github.com/ulikunitz/xz v0.5.10 // indirect github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect + gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec // indirect go.etcd.io/bbolt v1.3.6 // indirect go.etcd.io/etcd/api/v3 v3.5.0 // indirect go.etcd.io/etcd/client/pkg/v3 v3.5.0 // indirect diff --git a/itest/zero_conf_node.go b/itest/breez_client.go similarity index 87% rename from itest/zero_conf_node.go rename to itest/breez_client.go index 4b472f1..f872ae5 100644 --- a/itest/zero_conf_node.go +++ b/itest/breez_client.go @@ -8,19 +8,17 @@ import ( "path/filepath" "github.com/breez/lntest" - btcec "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/ecdsa" "github.com/btcsuite/btcd/chaincfg" - "github.com/decred/dcrd/dcrec/secp256k1/v4" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/zpay32" ) -type ZeroConfNode struct { +type breezClient struct { name string harness *lntest.TestHarness - lightningNode *lntest.CoreLightningNode - privKey *secp256k1.PrivateKey + lightningNode lntest.LightningNode scriptDir string } @@ -50,12 +48,7 @@ pip install pyln-client > /dev/null 2>&1 python %s ` -func NewZeroConfNode(h *lntest.TestHarness, m *lntest.Miner, name string) *ZeroConfNode { - privKey, err := btcec.NewPrivateKey() - lntest.CheckError(h.T, err) - - s := privKey.Serialize() - +func newClnBreezClient(h *lntest.TestHarness, m *lntest.Miner, name string) *breezClient { scriptDir, err := os.MkdirTemp(h.Dir, name) lntest.CheckError(h.T, err) pythonFilePath := filepath.Join(scriptDir, "zero_conf_plugin.py") @@ -88,7 +81,6 @@ func NewZeroConfNode(h *lntest.TestHarness, m *lntest.Miner, name string) *ZeroC h, m, name, - fmt.Sprintf("--dev-force-privkey=%x", s), fmt.Sprintf("--plugin=%s", pluginFilePath), // NOTE: max-concurrent-htlcs is 30 on mainnet by default. In cln V22.11 // there is a check for 'all dust' commitment transactions. The max @@ -99,12 +91,20 @@ func NewZeroConfNode(h *lntest.TestHarness, m *lntest.Miner, name string) *ZeroC "--max-concurrent-htlcs=30", ) - return &ZeroConfNode{ + return &breezClient{ name: name, harness: h, lightningNode: node, scriptDir: scriptDir, - privKey: privKey, + } +} + +func newLndBreezClient(h *lntest.TestHarness, m *lntest.Miner, name string) *breezClient { + lnd := lntest.NewLndNode(h, m, name) + return &breezClient{ + name: name, + harness: h, + lightningNode: lnd, } } @@ -122,7 +122,7 @@ type invoice struct { paymentPreimage []byte } -func (n *ZeroConfNode) GenerateInvoices(req generateInvoicesRequest) (invoice, invoice) { +func (n *breezClient) GenerateInvoices(req generateInvoicesRequest) (invoice, invoice) { preimage, err := GenerateRandomBytes(32) lntest.CheckError(n.harness.T, err) @@ -153,7 +153,7 @@ func (n *ZeroConfNode) GenerateInvoices(req generateInvoicesRequest) (invoice, i outerInvoice, err := outerInvoiceRaw.Encode(zpay32.MessageSigner{ SignCompact: func(msg []byte) ([]byte, error) { hash := sha256.Sum256(msg) - return ecdsa.SignCompact(n.privKey, hash[:], true) + return ecdsa.SignCompact(n.lightningNode.PrivateKey(), hash[:], true) }, }) lntest.CheckError(n.harness.T, err) diff --git a/itest/intercept_zero_conf_test.go b/itest/intercept_zero_conf_test.go index e0e64a9..079dcf4 100644 --- a/itest/intercept_zero_conf_test.go +++ b/itest/intercept_zero_conf_test.go @@ -8,15 +8,13 @@ import ( "github.com/stretchr/testify/assert" ) -func testOpenZeroConfChannelOnReceive(h *lntest.TestHarness, lsp LspNode, miner *lntest.Miner) { - alice := lntest.NewCoreLightningNode(h, miner, "Alice") - bob := NewZeroConfNode(h, miner, "Bob") - +func testOpenZeroConfChannelOnReceive(p *testParams) { + alice := lntest.NewCoreLightningNode(p.h, p.m, "Alice") alice.Fund(10000000) - lsp.LightningNode().Fund(10000000) + p.lsp.LightningNode().Fund(10000000) log.Print("Opening channel between Alice and the lsp") - channel := alice.OpenChannel(lsp.LightningNode(), &lntest.OpenChannelOptions{ + channel := alice.OpenChannel(p.lsp.LightningNode(), &lntest.OpenChannelOptions{ AmountSat: 1000000, }) alice.WaitForChannelReady(channel) @@ -25,50 +23,49 @@ func testOpenZeroConfChannelOnReceive(h *lntest.TestHarness, lsp LspNode, miner outerAmountMsat := uint64(2100000) innerAmountMsat := uint64(2100000) description := "Please pay me" - innerInvoice, outerInvoice := bob.GenerateInvoices(generateInvoicesRequest{ + innerInvoice, outerInvoice := p.BreezClient().GenerateInvoices(generateInvoicesRequest{ innerAmountMsat: innerAmountMsat, outerAmountMsat: outerAmountMsat, description: description, - lsp: lsp, + lsp: p.lsp, }) log.Print("Connecting bob to lspd") - bob.lightningNode.ConnectPeer(lsp.LightningNode()) + p.BreezClient().lightningNode.ConnectPeer(p.lsp.LightningNode()) // NOTE: We pretend to be paying fees to the lsp, but actually we won't. log.Printf("Registering payment with lsp") pretendAmount := outerAmountMsat - 2000000 - RegisterPayment(lsp, &lspd.PaymentInformation{ + RegisterPayment(p.lsp, &lspd.PaymentInformation{ PaymentHash: innerInvoice.paymentHash, PaymentSecret: innerInvoice.paymentSecret, - Destination: bob.lightningNode.NodeId(), + Destination: p.BreezClient().lightningNode.NodeId(), IncomingAmountMsat: int64(outerAmountMsat), OutgoingAmountMsat: int64(pretendAmount), }) log.Printf("Alice paying") payResp := alice.Pay(outerInvoice.bolt11) - bobInvoice := bob.lightningNode.GetInvoice(payResp.PaymentHash) + bobInvoice := p.BreezClient().lightningNode.GetInvoice(payResp.PaymentHash) - assert.Equal(h.T, payResp.PaymentPreimage, bobInvoice.PaymentPreimage) - assert.Equal(h.T, outerAmountMsat, bobInvoice.AmountReceivedMsat) + assert.Equal(p.t, payResp.PaymentPreimage, bobInvoice.PaymentPreimage) + assert.Equal(p.t, outerAmountMsat, bobInvoice.AmountReceivedMsat) // Make sure capacity is correct - chans := bob.lightningNode.GetChannels() - assert.Len(h.T, chans, 1) + chans := p.BreezClient().lightningNode.GetChannels() + assert.Len(p.t, chans, 1) c := chans[0] - AssertChannelCapacity(h.T, outerAmountMsat, c.CapacityMsat) + AssertChannelCapacity(p.t, outerAmountMsat, c.CapacityMsat) } -func testOpenZeroConfSingleHtlc(h *lntest.TestHarness, lsp LspNode, miner *lntest.Miner) { - alice := lntest.NewCoreLightningNode(h, miner, "Alice") - bob := NewZeroConfNode(h, miner, "Bob") +func testOpenZeroConfSingleHtlc(p *testParams) { + alice := lntest.NewCoreLightningNode(p.h, p.m, "Alice") alice.Fund(10000000) - lsp.LightningNode().Fund(10000000) + p.lsp.LightningNode().Fund(10000000) log.Print("Opening channel between Alice and the lsp") - channel := alice.OpenChannel(lsp.LightningNode(), &lntest.OpenChannelOptions{ + channel := alice.OpenChannel(p.lsp.LightningNode(), &lntest.OpenChannelOptions{ AmountSat: 1000000, }) channelId := alice.WaitForChannelReady(channel) @@ -77,40 +74,40 @@ func testOpenZeroConfSingleHtlc(h *lntest.TestHarness, lsp LspNode, miner *lntes outerAmountMsat := uint64(2100000) innerAmountMsat := uint64(2100000) description := "Please pay me" - innerInvoice, outerInvoice := bob.GenerateInvoices(generateInvoicesRequest{ + innerInvoice, outerInvoice := p.BreezClient().GenerateInvoices(generateInvoicesRequest{ innerAmountMsat: innerAmountMsat, outerAmountMsat: outerAmountMsat, description: description, - lsp: lsp, + lsp: p.lsp, }) log.Print("Connecting bob to lspd") - bob.lightningNode.ConnectPeer(lsp.LightningNode()) + p.BreezClient().lightningNode.ConnectPeer(p.lsp.LightningNode()) // NOTE: We pretend to be paying fees to the lsp, but actually we won't. log.Printf("Registering payment with lsp") pretendAmount := outerAmountMsat - 2000000 - RegisterPayment(lsp, &lspd.PaymentInformation{ + RegisterPayment(p.lsp, &lspd.PaymentInformation{ PaymentHash: innerInvoice.paymentHash, PaymentSecret: innerInvoice.paymentSecret, - Destination: bob.lightningNode.NodeId(), + Destination: p.BreezClient().lightningNode.NodeId(), IncomingAmountMsat: int64(outerAmountMsat), OutgoingAmountMsat: int64(pretendAmount), }) log.Printf("Alice paying") - route := constructRoute(lsp.LightningNode(), bob.lightningNode, channelId, lntest.NewShortChanIDFromString("1x0x0"), outerAmountMsat) + route := constructRoute(p.lsp.LightningNode(), p.BreezClient().lightningNode, channelId, lntest.NewShortChanIDFromString("1x0x0"), outerAmountMsat) payResp := alice.PayViaRoute(outerAmountMsat, outerInvoice.paymentHash, outerInvoice.paymentSecret, route) - bobInvoice := bob.lightningNode.GetInvoice(payResp.PaymentHash) + bobInvoice := p.BreezClient().lightningNode.GetInvoice(payResp.PaymentHash) - assert.Equal(h.T, payResp.PaymentPreimage, bobInvoice.PaymentPreimage) - assert.Equal(h.T, outerAmountMsat, bobInvoice.AmountReceivedMsat) + assert.Equal(p.t, payResp.PaymentPreimage, bobInvoice.PaymentPreimage) + assert.Equal(p.t, outerAmountMsat, bobInvoice.AmountReceivedMsat) // Make sure capacity is correct - chans := bob.lightningNode.GetChannels() - assert.Len(h.T, chans, 1) + chans := p.BreezClient().lightningNode.GetChannels() + assert.Len(p.t, chans, 1) c := chans[0] - AssertChannelCapacity(h.T, outerAmountMsat, c.CapacityMsat) + AssertChannelCapacity(p.t, outerAmountMsat, c.CapacityMsat) } func constructRoute( diff --git a/itest/lspd_test.go b/itest/lspd_test.go index 366f953..bf34d4f 100644 --- a/itest/lspd_test.go +++ b/itest/lspd_test.go @@ -13,25 +13,25 @@ var defaultTimeout time.Duration = time.Second * 120 func TestLspd(t *testing.T) { testCases := allTestCases - // runTests(t, testCases, "LND-lspd", func(h *lntest.TestHarness, m *lntest.Miner) LspNode { - // return NewLndLspdNode(h, m, "lsp") + // runTests(t, testCases, "LND-lspd", func(h *lntest.TestHarness, m *lntest.Miner) (LspNode, *breezClient) { + // return NewLndLspdNode(h, m, "lsp"), newLndBreezClient(h, m, "breez-client") // }) - runTests(t, testCases, "CLN-lspd", func(h *lntest.TestHarness, m *lntest.Miner) LspNode { - return NewClnLspdNode(h, m, "lsp") + runTests(t, testCases, "CLN-lspd", func(h *lntest.TestHarness, m *lntest.Miner) (LspNode, *breezClient) { + return NewClnLspdNode(h, m, "lsp"), newClnBreezClient(h, m, "breez-client") }) } -func runTests(t *testing.T, testCases []*testCase, prefix string, lspFunc func(h *lntest.TestHarness, m *lntest.Miner) LspNode) { +func runTests(t *testing.T, testCases []*testCase, prefix string, nodesFunc func(h *lntest.TestHarness, m *lntest.Miner) (LspNode, *breezClient)) { for _, testCase := range testCases { testCase := testCase t.Run(fmt.Sprintf("%s: %s", prefix, testCase.name), func(t *testing.T) { - runTest(t, testCase, prefix, lspFunc) + runTest(t, testCase, prefix, nodesFunc) }) } } -func runTest(t *testing.T, testCase *testCase, prefix string, lspFunc func(h *lntest.TestHarness, m *lntest.Miner) LspNode) { +func runTest(t *testing.T, testCase *testCase, prefix string, nodesFunc func(h *lntest.TestHarness, m *lntest.Miner) (LspNode, *breezClient)) { log.Printf("%s: Running test case '%s'", prefix, testCase.name) var dd time.Duration to := testCase.timeout @@ -42,20 +42,26 @@ func runTest(t *testing.T, testCase *testCase, prefix string, lspFunc func(h *ln deadline := time.Now().Add(to) log.Printf("Using deadline %v", deadline.String()) - harness := lntest.NewTestHarness(t, deadline) - defer harness.TearDown() + h := lntest.NewTestHarness(t, deadline) + defer h.TearDown() log.Printf("Creating miner") - miner := lntest.NewMiner(harness) + miner := lntest.NewMiner(h) log.Printf("Creating lsp") - lsp := lspFunc(harness, miner) + lsp, c := nodesFunc(h, miner) log.Printf("Run testcase") - testCase.test(harness, lsp, miner) + testCase.test(&testParams{ + t: t, + h: h, + m: miner, + c: c, + lsp: lsp, + }) } type testCase struct { name string - test func(h *lntest.TestHarness, lsp LspNode, miner *lntest.Miner) + test func(t *testParams) timeout time.Duration } diff --git a/itest/test_params.go b/itest/test_params.go new file mode 100644 index 0000000..5343940 --- /dev/null +++ b/itest/test_params.go @@ -0,0 +1,35 @@ +package itest + +import ( + "testing" + + "github.com/breez/lntest" +) + +type testParams struct { + t *testing.T + h *lntest.TestHarness + m *lntest.Miner + c *breezClient + lsp LspNode +} + +func (h *testParams) T() *testing.T { + return h.t +} + +func (h *testParams) Miner() *lntest.Miner { + return h.m +} + +func (h *testParams) Lsp() LspNode { + return h.lsp +} + +func (h *testParams) Harness() *lntest.TestHarness { + return h.h +} + +func (h *testParams) BreezClient() *breezClient { + return h.c +} diff --git a/itest/zero_reserve_test.go b/itest/zero_reserve_test.go index 36aa9d5..bc218ce 100644 --- a/itest/zero_reserve_test.go +++ b/itest/zero_reserve_test.go @@ -8,15 +8,13 @@ import ( "github.com/stretchr/testify/assert" ) -func testZeroReserve(h *lntest.TestHarness, lsp LspNode, miner *lntest.Miner) { - alice := lntest.NewCoreLightningNode(h, miner, "Alice") - bob := NewZeroConfNode(h, miner, "Bob") - +func testZeroReserve(p *testParams) { + alice := lntest.NewCoreLightningNode(p.h, p.m, "Alice") alice.Fund(10000000) - lsp.LightningNode().Fund(10000000) + p.lsp.LightningNode().Fund(10000000) log.Print("Opening channel between Alice and the lsp") - channel := alice.OpenChannel(lsp.LightningNode(), &lntest.OpenChannelOptions{ + channel := alice.OpenChannel(p.lsp.LightningNode(), &lntest.OpenChannelOptions{ AmountSat: 1000000, }) channelId := alice.WaitForChannelReady(channel) @@ -25,36 +23,36 @@ func testZeroReserve(h *lntest.TestHarness, lsp LspNode, miner *lntest.Miner) { outerAmountMsat := uint64(2100000) innerAmountMsat := uint64(2100000) description := "Please pay me" - innerInvoice, outerInvoice := bob.GenerateInvoices(generateInvoicesRequest{ + innerInvoice, outerInvoice := p.BreezClient().GenerateInvoices(generateInvoicesRequest{ innerAmountMsat: innerAmountMsat, outerAmountMsat: outerAmountMsat, description: description, - lsp: lsp, + lsp: p.lsp, }) log.Print("Connecting bob to lspd") - bob.lightningNode.ConnectPeer(lsp.LightningNode()) + p.BreezClient().lightningNode.ConnectPeer(p.lsp.LightningNode()) // NOTE: We pretend to be paying fees to the lsp, but actually we won't. log.Printf("Registering payment with lsp") pretendAmount := outerAmountMsat - 2000000 - RegisterPayment(lsp, &lspd.PaymentInformation{ + RegisterPayment(p.lsp, &lspd.PaymentInformation{ PaymentHash: innerInvoice.paymentHash, PaymentSecret: innerInvoice.paymentSecret, - Destination: bob.lightningNode.NodeId(), + Destination: p.BreezClient().lightningNode.NodeId(), IncomingAmountMsat: int64(outerAmountMsat), OutgoingAmountMsat: int64(pretendAmount), }) log.Printf("Alice paying") - route := constructRoute(lsp.LightningNode(), bob.lightningNode, channelId, lntest.NewShortChanIDFromString("1x0x0"), outerAmountMsat) + route := constructRoute(p.lsp.LightningNode(), p.BreezClient().lightningNode, channelId, lntest.NewShortChanIDFromString("1x0x0"), outerAmountMsat) alice.PayViaRoute(outerAmountMsat, outerInvoice.paymentHash, outerInvoice.paymentSecret, route) // Make sure balance is correct - chans := bob.lightningNode.GetChannels() - assert.Len(h.T, chans, 1) + chans := p.BreezClient().lightningNode.GetChannels() + assert.Len(p.t, chans, 1) c := chans[0] - assert.Equal(h.T, c.RemoteReserveMsat, c.CapacityMsat/100) - assert.Zero(h.T, c.LocalReserveMsat) + assert.Equal(p.t, c.RemoteReserveMsat, c.CapacityMsat/100) + assert.Zero(p.t, c.LocalReserveMsat) } From 4b6eaf37739d33a3026b8b5c92509b02e8d4d015 Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Thu, 15 Dec 2022 11:00:38 +0100 Subject: [PATCH 106/214] use time.After instead of sleep --- forwarding_history.go | 7 ++++--- intercept.go | 2 +- itest/postgres.go | 4 ++-- lnd_interceptor.go | 4 +++- main.go | 2 ++ 5 files changed, 12 insertions(+), 7 deletions(-) diff --git a/forwarding_history.go b/forwarding_history.go index d511ba5..56c566d 100644 --- a/forwarding_history.go +++ b/forwarding_history.go @@ -44,7 +44,7 @@ func channelsSynchronize(client *LndClient) { if err != nil { log.Printf("chainNotifierClient.RegisterBlockEpochNtfn(): %v", err) cancel() - time.Sleep(1 * time.Second) + <-time.After(time.Second) continue } @@ -52,10 +52,11 @@ func channelsSynchronize(client *LndClient) { _, err := stream.Recv() if err != nil { log.Printf("stream.Recv: %v", err) + <-time.After(time.Second) break } if lastSync.Add(5 * time.Minute).Before(time.Now()) { - time.Sleep(30 * time.Second) + <-time.After(30 * time.Second) err = channelsSynchronizeOnce(client) lastSync = time.Now() log.Printf("channelsSynchronizeOnce() err: %v", err) @@ -102,7 +103,7 @@ func forwardingHistorySynchronize(client *LndClient) { for { err := forwardingHistorySynchronizeOnce(client) log.Printf("forwardingHistorySynchronizeOnce() err: %v", err) - time.Sleep(1 * time.Minute) + <-time.After(1 * time.Minute) } } diff --git a/intercept.go b/intercept.go index a7a8fff..ed04a45 100644 --- a/intercept.go +++ b/intercept.go @@ -198,7 +198,7 @@ func intercept(reqPaymentHash []byte, reqOutgoingAmountMsat uint64, reqOutgoingE log.Printf("Stop retrying getChannel(%v, %v)", destination, channelPoint.String()) break } - time.Sleep(1 * time.Second) + <-time.After(1 * time.Second) } log.Printf("Error: Channel failed to opened... timed out. ") diff --git a/itest/postgres.go b/itest/postgres.go index 1d4fbfd..bc3a89d 100644 --- a/itest/postgres.go +++ b/itest/postgres.go @@ -107,10 +107,10 @@ HealthCheck: break HealthCheck } - time.Sleep(50 * time.Millisecond) + <-time.After(50 * time.Millisecond) } default: - time.Sleep(200 * time.Millisecond) + <-time.After(200 * time.Millisecond) } } diff --git a/lnd_interceptor.go b/lnd_interceptor.go index ed43d82..4c13c8b 100644 --- a/lnd_interceptor.go +++ b/lnd_interceptor.go @@ -58,12 +58,13 @@ func (i *LndHtlcInterceptor) intercept() error { return nil } + log.Printf("Connecting LND HTLC interceptor.") cancellableCtx, cancel := context.WithCancel(context.Background()) interceptorClient, err := i.client.routerClient.HtlcInterceptor(cancellableCtx) if err != nil { log.Printf("routerClient.HtlcInterceptor(): %v", err) cancel() - time.Sleep(1 * time.Second) + <-time.After(time.Second) continue } @@ -133,6 +134,7 @@ func (i *LndHtlcInterceptor) intercept() error { } cancel() + <-time.After(time.Second) } } diff --git a/main.go b/main.go index a181e51..e447194 100644 --- a/main.go +++ b/main.go @@ -65,6 +65,8 @@ func main() { if err != nil { log.Fatalf("client.GetInfo() error: %v", err) } + log.Printf("Connected to node '%s', alias '%s'", info.Pubkey, info.Alias) + if nodeName == "" { nodeName = info.Alias } From 08f2f73b5d61db98c7f6788249d102102509fab1 Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Fri, 16 Dec 2022 10:32:30 +0100 Subject: [PATCH 107/214] make LND tests work --- go.mod | 2 +- itest/breez_client.go | 42 +++++++++++++++++++++++++++++-- itest/intercept_zero_conf_test.go | 25 ++++++++++++------ itest/lspd_node.go | 22 ++++++++++------ itest/lspd_test.go | 6 ++--- itest/test_common.go | 15 +++++++++++ itest/zero_reserve_test.go | 11 +++++--- 7 files changed, 99 insertions(+), 24 deletions(-) diff --git a/go.mod b/go.mod index bdd0d90..ef373b1 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.19 require ( github.com/aws/aws-sdk-go v1.30.20 - github.com/breez/lntest v0.0.9 + github.com/breez/lntest v0.0.10 github.com/btcsuite/btcd v0.23.3 github.com/btcsuite/btcd/btcec/v2 v2.2.1 github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 diff --git a/itest/breez_client.go b/itest/breez_client.go index f872ae5..7465c33 100644 --- a/itest/breez_client.go +++ b/itest/breez_client.go @@ -3,11 +3,13 @@ package itest import ( "bufio" "crypto/sha256" + "flag" "fmt" "os" "path/filepath" "github.com/breez/lntest" + "github.com/breez/lntest/lnd" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/ecdsa" "github.com/btcsuite/btcd/chaincfg" @@ -77,7 +79,7 @@ func newClnBreezClient(h *lntest.TestHarness, m *lntest.Miner, name string) *bre lntest.CheckError(h.T, err) pluginFile.Close() - node := lntest.NewCoreLightningNode( + node := lntest.NewClnNode( h, m, name, @@ -99,8 +101,19 @@ func newClnBreezClient(h *lntest.TestHarness, m *lntest.Miner, name string) *bre } } +var lndMobileExecutable = flag.String( + "lndmobileexec", "", "full path to lnd mobile binary", +) + func newLndBreezClient(h *lntest.TestHarness, m *lntest.Miner, name string) *breezClient { - lnd := lntest.NewLndNode(h, m, name) + lnd := lntest.NewLndNodeFromBinary(h, m, name, *lndMobileExecutable, + "--protocol.zero-conf", + "--protocol.option-scid-alias", + "--bitcoin.defaultchanconfs=0", + ) + + go startChannelAcceptor(h, lnd) + return &breezClient{ name: name, harness: h, @@ -108,6 +121,31 @@ func newLndBreezClient(h *lntest.TestHarness, m *lntest.Miner, name string) *bre } } +func startChannelAcceptor(h *lntest.TestHarness, n *lntest.LndNode) error { + client, err := n.LightningClient().ChannelAcceptor(h.Ctx) + lntest.CheckError(h.T, err) + + for { + request, err := client.Recv() + if err != nil { + return err + } + + private := request.ChannelFlags&uint32(lnwire.FFAnnounceChannel) == 0 + resp := &lnd.ChannelAcceptResponse{ + PendingChanId: request.PendingChanId, + Accept: private, + } + if request.WantsZeroConf { + resp.MinAcceptDepth = 0 + resp.ZeroConf = true + } + + err = client.Send(resp) + lntest.CheckError(h.T, err) + } +} + type generateInvoicesRequest struct { innerAmountMsat uint64 outerAmountMsat uint64 diff --git a/itest/intercept_zero_conf_test.go b/itest/intercept_zero_conf_test.go index 079dcf4..f5d5ab5 100644 --- a/itest/intercept_zero_conf_test.go +++ b/itest/intercept_zero_conf_test.go @@ -2,26 +2,29 @@ package itest import ( "log" + "time" "github.com/breez/lntest" lspd "github.com/breez/lspd/rpc" "github.com/stretchr/testify/assert" ) +var htlcInterceptorDelay = time.Second * 7 + func testOpenZeroConfChannelOnReceive(p *testParams) { - alice := lntest.NewCoreLightningNode(p.h, p.m, "Alice") + alice := lntest.NewClnNode(p.h, p.m, "Alice") alice.Fund(10000000) p.lsp.LightningNode().Fund(10000000) log.Print("Opening channel between Alice and the lsp") channel := alice.OpenChannel(p.lsp.LightningNode(), &lntest.OpenChannelOptions{ - AmountSat: 1000000, + AmountSat: publicChanAmount, }) alice.WaitForChannelReady(channel) log.Printf("Adding bob's invoices") outerAmountMsat := uint64(2100000) - innerAmountMsat := uint64(2100000) + innerAmountMsat := calculateInnerAmountMsat(p.lsp, outerAmountMsat) description := "Please pay me" innerInvoice, outerInvoice := p.BreezClient().GenerateInvoices(generateInvoicesRequest{ innerAmountMsat: innerAmountMsat, @@ -44,12 +47,15 @@ func testOpenZeroConfChannelOnReceive(p *testParams) { OutgoingAmountMsat: int64(pretendAmount), }) + // TODO: Fix race waiting for htlc interceptor. + log.Printf("Waiting %v to allow htlc interceptor to activate.", htlcInterceptorDelay) + <-time.After(htlcInterceptorDelay) log.Printf("Alice paying") payResp := alice.Pay(outerInvoice.bolt11) bobInvoice := p.BreezClient().lightningNode.GetInvoice(payResp.PaymentHash) assert.Equal(p.t, payResp.PaymentPreimage, bobInvoice.PaymentPreimage) - assert.Equal(p.t, outerAmountMsat, bobInvoice.AmountReceivedMsat) + assert.Equal(p.t, innerAmountMsat, bobInvoice.AmountReceivedMsat) // Make sure capacity is correct chans := p.BreezClient().lightningNode.GetChannels() @@ -59,20 +65,20 @@ func testOpenZeroConfChannelOnReceive(p *testParams) { } func testOpenZeroConfSingleHtlc(p *testParams) { - alice := lntest.NewCoreLightningNode(p.h, p.m, "Alice") + alice := lntest.NewClnNode(p.h, p.m, "Alice") alice.Fund(10000000) p.lsp.LightningNode().Fund(10000000) log.Print("Opening channel between Alice and the lsp") channel := alice.OpenChannel(p.lsp.LightningNode(), &lntest.OpenChannelOptions{ - AmountSat: 1000000, + AmountSat: publicChanAmount, }) channelId := alice.WaitForChannelReady(channel) log.Printf("Adding bob's invoices") outerAmountMsat := uint64(2100000) - innerAmountMsat := uint64(2100000) + innerAmountMsat := calculateInnerAmountMsat(p.lsp, outerAmountMsat) description := "Please pay me" innerInvoice, outerInvoice := p.BreezClient().GenerateInvoices(generateInvoicesRequest{ innerAmountMsat: innerAmountMsat, @@ -95,13 +101,16 @@ func testOpenZeroConfSingleHtlc(p *testParams) { OutgoingAmountMsat: int64(pretendAmount), }) + // TODO: Fix race waiting for htlc interceptor. + log.Printf("Waiting %v to allow htlc interceptor to activate.", htlcInterceptorDelay) + <-time.After(htlcInterceptorDelay) log.Printf("Alice paying") route := constructRoute(p.lsp.LightningNode(), p.BreezClient().lightningNode, channelId, lntest.NewShortChanIDFromString("1x0x0"), outerAmountMsat) payResp := alice.PayViaRoute(outerAmountMsat, outerInvoice.paymentHash, outerInvoice.paymentSecret, route) bobInvoice := p.BreezClient().lightningNode.GetInvoice(payResp.PaymentHash) assert.Equal(p.t, payResp.PaymentPreimage, bobInvoice.PaymentPreimage) - assert.Equal(p.t, outerAmountMsat, bobInvoice.AmountReceivedMsat) + assert.Equal(p.t, innerAmountMsat, bobInvoice.AmountReceivedMsat) // Make sure capacity is correct chans := p.BreezClient().lightningNode.GetChannels() diff --git a/itest/lspd_node.go b/itest/lspd_node.go index c5dd445..e845d5b 100644 --- a/itest/lspd_node.go +++ b/itest/lspd_node.go @@ -43,11 +43,12 @@ type LspNode interface { Rpc() lspd.ChannelOpenerClient NodeId() []byte LightningNode() lntest.LightningNode + SupportsChargingFees() bool } type ClnLspNode struct { harness *lntest.TestHarness - lightningNode *lntest.CoreLightningNode + lightningNode *lntest.ClnNode rpc lspd.ChannelOpenerClient publicKey btcec.PublicKey eciesPublicKey ecies.PublicKey @@ -72,11 +73,11 @@ func (c *ClnLspNode) Rpc() lspd.ChannelOpenerClient { func (l *ClnLspNode) TearDown() error { // NOTE: The lightningnode will be torn down on its own. - return l.postgresBackend.Shutdown(l.harness.Ctx) + return l.postgresBackend.Shutdown(context.Background()) } func (l *ClnLspNode) Cleanup() error { - return l.postgresBackend.Cleanup(l.harness.Ctx) + return l.postgresBackend.Cleanup(context.Background()) } func (l *ClnLspNode) NodeId() []byte { @@ -87,6 +88,10 @@ func (l *ClnLspNode) LightningNode() lntest.LightningNode { return l.lightningNode } +func (l *ClnLspNode) SupportsChargingFees() bool { + return false +} + type LndLspNode struct { harness *lntest.TestHarness lightningNode *lntest.LndNode @@ -113,6 +118,9 @@ func (c *LndLspNode) EciesPublicKey() *ecies.PublicKey { func (c *LndLspNode) Rpc() lspd.ChannelOpenerClient { return c.rpc } +func (l *LndLspNode) SupportsChargingFees() bool { + return true +} func (l *LndLspNode) TearDown() error { // NOTE: The lightningnode will be torn down on its own. @@ -130,11 +138,11 @@ func (l *LndLspNode) TearDown() error { } } - return l.postgresBackend.Shutdown(l.harness.Ctx) + return l.postgresBackend.Shutdown(context.Background()) } func (l *LndLspNode) Cleanup() error { - return l.postgresBackend.Cleanup(l.harness.Ctx) + return l.postgresBackend.Cleanup(context.Background()) } func (l *LndLspNode) NodeId() []byte { @@ -156,7 +164,7 @@ func NewClnLspdNode(h *lntest.TestHarness, m *lntest.Miner, name string) LspNode "--dev-allowdustreserve=true", } - lightningNode := lntest.NewCoreLightningNode(h, m, name, args...) + lightningNode := lntest.NewClnNode(h, m, name, args...) conn, err := grpc.Dial( grpcAddress, @@ -187,7 +195,7 @@ func NewLndLspdNode(h *lntest.TestHarness, m *lntest.Miner, name string) LspNode "--protocol.option-scid-alias", "--requireinterceptor", "--bitcoin.defaultchanconfs=0", - "--bitcoin.chanreservescript=\"0\"", + fmt.Sprintf("--bitcoin.chanreservescript=\"0 if (chanAmt != %d) else chanAmt/100\"", publicChanAmount), fmt.Sprintf("--bitcoin.basefee=%d", lspBaseFeeMsat), fmt.Sprintf("--bitcoin.feerate=%d", lspFeeRatePpm), fmt.Sprintf("--bitcoin.timelockdelta=%d", lspCltvDelta), diff --git a/itest/lspd_test.go b/itest/lspd_test.go index bf34d4f..6ae6c37 100644 --- a/itest/lspd_test.go +++ b/itest/lspd_test.go @@ -13,9 +13,9 @@ var defaultTimeout time.Duration = time.Second * 120 func TestLspd(t *testing.T) { testCases := allTestCases - // runTests(t, testCases, "LND-lspd", func(h *lntest.TestHarness, m *lntest.Miner) (LspNode, *breezClient) { - // return NewLndLspdNode(h, m, "lsp"), newLndBreezClient(h, m, "breez-client") - // }) + runTests(t, testCases, "LND-lspd", func(h *lntest.TestHarness, m *lntest.Miner) (LspNode, *breezClient) { + return NewLndLspdNode(h, m, "lsp"), newLndBreezClient(h, m, "breez-client") + }) runTests(t, testCases, "CLN-lspd", func(h *lntest.TestHarness, m *lntest.Miner) (LspNode, *breezClient) { return NewClnLspdNode(h, m, "lsp"), newClnBreezClient(h, m, "breez-client") diff --git a/itest/test_common.go b/itest/test_common.go index 926d673..c83b8fe 100644 --- a/itest/test_common.go +++ b/itest/test_common.go @@ -25,3 +25,18 @@ func AssertChannelCapacity( ) { assert.Equal(t, ((outerAmountMsat/1000)+100000)*1000, capacityMsat) } + +func calculateInnerAmountMsat(lsp LspNode, outerAmountMsat uint64) uint64 { + if lsp.SupportsChargingFees() { + fee := outerAmountMsat * 40 / 10_000 / 1_000 * 1_000 + if fee < 2000000 { + fee = 2000000 + } + + return outerAmountMsat - fee + } else { + return outerAmountMsat + } +} + +var publicChanAmount uint64 = 1000183 diff --git a/itest/zero_reserve_test.go b/itest/zero_reserve_test.go index bc218ce..ea6e8d6 100644 --- a/itest/zero_reserve_test.go +++ b/itest/zero_reserve_test.go @@ -2,6 +2,7 @@ package itest import ( "log" + "time" "github.com/breez/lntest" lspd "github.com/breez/lspd/rpc" @@ -9,19 +10,19 @@ import ( ) func testZeroReserve(p *testParams) { - alice := lntest.NewCoreLightningNode(p.h, p.m, "Alice") + alice := lntest.NewClnNode(p.h, p.m, "Alice") alice.Fund(10000000) p.lsp.LightningNode().Fund(10000000) log.Print("Opening channel between Alice and the lsp") channel := alice.OpenChannel(p.lsp.LightningNode(), &lntest.OpenChannelOptions{ - AmountSat: 1000000, + AmountSat: publicChanAmount, }) channelId := alice.WaitForChannelReady(channel) log.Printf("Adding bob's invoices") outerAmountMsat := uint64(2100000) - innerAmountMsat := uint64(2100000) + innerAmountMsat := calculateInnerAmountMsat(p.lsp, outerAmountMsat) description := "Please pay me" innerInvoice, outerInvoice := p.BreezClient().GenerateInvoices(generateInvoicesRequest{ innerAmountMsat: innerAmountMsat, @@ -44,6 +45,9 @@ func testZeroReserve(p *testParams) { OutgoingAmountMsat: int64(pretendAmount), }) + // TODO: Fix race waiting for htlc interceptor. + log.Printf("Waiting %v to allow htlc interceptor to activate.", htlcInterceptorDelay) + <-time.After(htlcInterceptorDelay) log.Printf("Alice paying") route := constructRoute(p.lsp.LightningNode(), p.BreezClient().lightningNode, channelId, lntest.NewShortChanIDFromString("1x0x0"), outerAmountMsat) alice.PayViaRoute(outerAmountMsat, outerInvoice.paymentHash, outerInvoice.paymentSecret, route) @@ -54,5 +58,6 @@ func testZeroReserve(p *testParams) { c := chans[0] assert.Equal(p.t, c.RemoteReserveMsat, c.CapacityMsat/100) + log.Printf("local reserve: %d, remote reserve: %d", c.LocalReserveMsat, c.RemoteReserveMsat) assert.Zero(p.t, c.LocalReserveMsat) } From 89a68fed4f1ec3007ebfb61315b5ca6322c94815 Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Fri, 16 Dec 2022 11:36:15 +0100 Subject: [PATCH 108/214] update readme for itest --- README.md | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index f372f75..3a1725f 100644 --- a/README.md +++ b/README.md @@ -60,17 +60,31 @@ In order to run the integration tests, you need: - Docker running - python3 installed - A development build of lightningd v22.11 -- lnd v0.15.4 -- bitcoind v23.0 -- bitcoin-cli v23.0 +- lnd v0.15.4 lsp version https://github.com/breez/lnd/tree/breez-node-v0.15.4 +- lnd v0.15.3 breez client version https://github.com/breez/lnd/commit/e1570b327b5de52d03817ad516d0bdfa71797c64 +- bitcoind (tested with v23.0) +- bitcoin-cli (tested with v23.0) - build of lspd To run the integration tests, run the following command from the lspd root directory (replacing the appropriate paths). ``` -go test -v ./itest --lightningdexec /full/path/to/lightningd --lndexec /full/path/to/lnd --bitcoindexec /full/path/to/bitcoind --bitcoincliexec /full/path/to/bitcoin-cli --lspdexec /full/path/to/lspd --lspdmigrationsdir /full/path/to/lspd/postgresql/migrations --testdir /path/to/test/output/dir +go test -v ./itest \ + --lightningdexec /full/path/to/lightningd \ + --lndexec /full/path/to/lnd \ + --lndmobileexec /full/path/to/lnd \ + --lspdexec /full/path/to/lspd \ + --lspdmigrationsdir /full/path/to/lspd/postgresql/migrations ``` -- Optional: `--preservelogs` persists only the logs in the testing directory -- Optional: `--preservestate` preserves all artifacts from the lightning nodes, miners, postgres container and startup scripts -Alternatively, if `lightningd`, `lnd`, `bitcoind`, `bitcoin-cli` or `lspd` exec flags are omitted, these binaries are taken from `$PATH` instead if found. \ No newline at end of file +- Required: `--lightningdexec` Full path to lightningd development build executable. Defaults to `lightningd` in `$PATH`. +- Required: `--lndexec` Full path to LSP LND executable. Defaults to `lnd` in `$PATH`. +- Required: `--lndmobileexec` Full path to Breez mobile client LND executable. No default. +- Required: `--lspdexec` Full path to `lspd` executable to test. Defaults to `lspd` in `$PATH`. +- Required: `--lspdmigrationsdir` Path to directory containing postgres migrations for lspd. (Should be `./postgresql/migrations`) +- Recommended: `--bitcoindexec` Full path to `bitcoind`. Defaults to `bitcoind` in `$PATH`. +- Recommended: `--bitcoincliexec` Full path to `bitcoin-cli`. Defaults to `bitcoin-cli` in `$PATH`. +- Recommended: `--testdir` uses the testdir as root directory for test files. Recommended because the CLN `lightning-rpc` socket max path length is 104-108 characters. Defaults to a temp directory (which has a long path length usually). +- Optional: `--preservelogs` persists only the logs in the testing directory. +- Optional: `--preservestate` preserves all artifacts from the lightning nodes, miners, postgres container and startup scripts. +- Optional: `--dumplogs` dumps all logs to the console after a test is complete. From f538f75d2cb371592882161df6515d8c7e3be026 Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Fri, 16 Dec 2022 18:22:04 +0100 Subject: [PATCH 109/214] refactor to allow start/stop --- go.mod | 2 +- itest/breez_client.go | 153 ++------------ itest/cln_breez_client.go | 155 ++++++++++++++ itest/cln_lspd_node.go | 133 ++++++++++++ itest/intercept_zero_conf_test.go | 47 +++-- itest/lnd_breez_client.go | 108 ++++++++++ itest/lnd_lspd_node.go | 230 +++++++++++++++++++++ itest/lspd_node.go | 326 ++++++++++-------------------- itest/lspd_test.go | 22 +- itest/postgres.go | 241 ++++++++++++++-------- itest/test_params.go | 4 +- itest/zero_reserve_test.go | 22 +- 12 files changed, 953 insertions(+), 490 deletions(-) create mode 100644 itest/cln_breez_client.go create mode 100644 itest/cln_lspd_node.go create mode 100644 itest/lnd_breez_client.go create mode 100644 itest/lnd_lspd_node.go diff --git a/go.mod b/go.mod index ef373b1..77041de 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.19 require ( github.com/aws/aws-sdk-go v1.30.20 - github.com/breez/lntest v0.0.10 + github.com/breez/lntest v0.0.11 github.com/btcsuite/btcd v0.23.3 github.com/btcsuite/btcd/btcec/v2 v2.2.1 github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 diff --git a/itest/breez_client.go b/itest/breez_client.go index 7465c33..8334acd 100644 --- a/itest/breez_client.go +++ b/itest/breez_client.go @@ -1,15 +1,9 @@ package itest import ( - "bufio" "crypto/sha256" - "flag" - "fmt" - "os" - "path/filepath" "github.com/breez/lntest" - "github.com/breez/lntest/lnd" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/ecdsa" "github.com/btcsuite/btcd/chaincfg" @@ -17,133 +11,12 @@ import ( "github.com/lightningnetwork/lnd/zpay32" ) -type breezClient struct { - name string - harness *lntest.TestHarness - lightningNode lntest.LightningNode - scriptDir string -} - -var pluginContent string = `#!/usr/bin/env python3 -"""Use the openchannel hook to selectively opt-into zeroconf -""" - -from pyln.client import Plugin - -plugin = Plugin() - - -@plugin.hook('openchannel') -def on_openchannel(openchannel, plugin, **kwargs): - plugin.log(repr(openchannel)) - mindepth = int(0) - - plugin.log(f"This peer is in the zeroconf allowlist, setting mindepth={mindepth}") - return {'result': 'continue', 'mindepth': mindepth} - -plugin.run() -` - -var pluginStartupContent string = `python3 -m venv %s > /dev/null 2>&1 -source %s > /dev/null 2>&1 -pip install pyln-client > /dev/null 2>&1 -python %s -` - -func newClnBreezClient(h *lntest.TestHarness, m *lntest.Miner, name string) *breezClient { - scriptDir, err := os.MkdirTemp(h.Dir, name) - lntest.CheckError(h.T, err) - pythonFilePath := filepath.Join(scriptDir, "zero_conf_plugin.py") - pythonFile, err := os.OpenFile(pythonFilePath, os.O_CREATE|os.O_WRONLY, 0755) - lntest.CheckError(h.T, err) - - pythonWriter := bufio.NewWriter(pythonFile) - _, err = pythonWriter.WriteString(pluginContent) - lntest.CheckError(h.T, err) - - err = pythonWriter.Flush() - lntest.CheckError(h.T, err) - pythonFile.Close() - - pluginFilePath := filepath.Join(scriptDir, "start_zero_conf_plugin.sh") - pluginFile, err := os.OpenFile(pluginFilePath, os.O_CREATE|os.O_WRONLY, 0755) - lntest.CheckError(h.T, err) - - pluginWriter := bufio.NewWriter(pluginFile) - venvDir := filepath.Join(scriptDir, "venv") - activatePath := filepath.Join(venvDir, "bin", "activate") - _, err = pluginWriter.WriteString(fmt.Sprintf(pluginStartupContent, venvDir, activatePath, pythonFilePath)) - lntest.CheckError(h.T, err) - - err = pluginWriter.Flush() - lntest.CheckError(h.T, err) - pluginFile.Close() - - node := lntest.NewClnNode( - h, - m, - name, - fmt.Sprintf("--plugin=%s", pluginFilePath), - // NOTE: max-concurrent-htlcs is 30 on mainnet by default. In cln V22.11 - // there is a check for 'all dust' commitment transactions. The max - // concurrent HTLCs of both sides of the channel * dust limit must be - // lower than the channel capacity in order to open a zero conf zero - // reserve channel. Relevant code: - // https://github.com/ElementsProject/lightning/blob/774d16a72e125e4ae4e312b9e3307261983bec0e/openingd/openingd.c#L481-L520 - "--max-concurrent-htlcs=30", - ) - - return &breezClient{ - name: name, - harness: h, - lightningNode: node, - scriptDir: scriptDir, - } -} - -var lndMobileExecutable = flag.String( - "lndmobileexec", "", "full path to lnd mobile binary", -) - -func newLndBreezClient(h *lntest.TestHarness, m *lntest.Miner, name string) *breezClient { - lnd := lntest.NewLndNodeFromBinary(h, m, name, *lndMobileExecutable, - "--protocol.zero-conf", - "--protocol.option-scid-alias", - "--bitcoin.defaultchanconfs=0", - ) - - go startChannelAcceptor(h, lnd) - - return &breezClient{ - name: name, - harness: h, - lightningNode: lnd, - } -} - -func startChannelAcceptor(h *lntest.TestHarness, n *lntest.LndNode) error { - client, err := n.LightningClient().ChannelAcceptor(h.Ctx) - lntest.CheckError(h.T, err) - - for { - request, err := client.Recv() - if err != nil { - return err - } - - private := request.ChannelFlags&uint32(lnwire.FFAnnounceChannel) == 0 - resp := &lnd.ChannelAcceptResponse{ - PendingChanId: request.PendingChanId, - Accept: private, - } - if request.WantsZeroConf { - resp.MinAcceptDepth = 0 - resp.ZeroConf = true - } - - err = client.Send(resp) - lntest.CheckError(h.T, err) - } +type BreezClient interface { + Name() string + Harness() *lntest.TestHarness + Node() lntest.LightningNode + Start() + Stop() error } type generateInvoicesRequest struct { @@ -160,20 +33,20 @@ type invoice struct { paymentPreimage []byte } -func (n *breezClient) GenerateInvoices(req generateInvoicesRequest) (invoice, invoice) { +func GenerateInvoices(n BreezClient, req generateInvoicesRequest) (invoice, invoice) { preimage, err := GenerateRandomBytes(32) - lntest.CheckError(n.harness.T, err) + lntest.CheckError(n.Harness().T, err) lspNodeId, err := btcec.ParsePubKey(req.lsp.NodeId()) - lntest.CheckError(n.harness.T, err) + lntest.CheckError(n.Harness().T, err) - innerInvoice := n.lightningNode.CreateBolt11Invoice(&lntest.CreateInvoiceOptions{ + innerInvoice := n.Node().CreateBolt11Invoice(&lntest.CreateInvoiceOptions{ AmountMsat: req.innerAmountMsat, Description: &req.description, Preimage: &preimage, }) outerInvoiceRaw, err := zpay32.Decode(innerInvoice.Bolt11, &chaincfg.RegressionNetParams) - lntest.CheckError(n.harness.T, err) + lntest.CheckError(n.Harness().T, err) milliSat := lnwire.MilliSatoshi(req.outerAmountMsat) outerInvoiceRaw.MilliSat = &milliSat @@ -191,10 +64,10 @@ func (n *breezClient) GenerateInvoices(req generateInvoicesRequest) (invoice, in outerInvoice, err := outerInvoiceRaw.Encode(zpay32.MessageSigner{ SignCompact: func(msg []byte) ([]byte, error) { hash := sha256.Sum256(msg) - return ecdsa.SignCompact(n.lightningNode.PrivateKey(), hash[:], true) + return ecdsa.SignCompact(n.Node().PrivateKey(), hash[:], true) }, }) - lntest.CheckError(n.harness.T, err) + lntest.CheckError(n.Harness().T, err) inner := invoice{ bolt11: innerInvoice.Bolt11, diff --git a/itest/cln_breez_client.go b/itest/cln_breez_client.go new file mode 100644 index 0000000..ebd3afe --- /dev/null +++ b/itest/cln_breez_client.go @@ -0,0 +1,155 @@ +package itest + +import ( + "bufio" + "fmt" + "os" + "path/filepath" + "sync" + + "github.com/breez/lntest" +) + +var pluginContent string = `#!/usr/bin/env python3 +"""Use the openchannel hook to selectively opt-into zeroconf +""" + +from pyln.client import Plugin + +plugin = Plugin() + + +@plugin.hook('openchannel') +def on_openchannel(openchannel, plugin, **kwargs): + plugin.log(repr(openchannel)) + mindepth = int(0) + + plugin.log(f"This peer is in the zeroconf allowlist, setting mindepth={mindepth}") + return {'result': 'continue', 'mindepth': mindepth} + +plugin.run() +` + +var pluginStartupContent string = `python3 -m venv %s > /dev/null 2>&1 +source %s > /dev/null 2>&1 +pip install pyln-client > /dev/null 2>&1 +python %s +` + +type clnBreezClient struct { + name string + scriptDir string + pluginFilePath string + harness *lntest.TestHarness + isInitialized bool + node *lntest.ClnNode + mtx sync.Mutex +} + +func newClnBreezClient(h *lntest.TestHarness, m *lntest.Miner, name string) BreezClient { + scriptDir := h.GetDirectory(name) + pluginFilePath := filepath.Join(scriptDir, "start_zero_conf_plugin.sh") + node := lntest.NewClnNode( + h, + m, + name, + fmt.Sprintf("--plugin=%s", pluginFilePath), + // NOTE: max-concurrent-htlcs is 30 on mainnet by default. In cln V22.11 + // there is a check for 'all dust' commitment transactions. The max + // concurrent HTLCs of both sides of the channel * dust limit must be + // lower than the channel capacity in order to open a zero conf zero + // reserve channel. Relevant code: + // https://github.com/ElementsProject/lightning/blob/774d16a72e125e4ae4e312b9e3307261983bec0e/openingd/openingd.c#L481-L520 + "--max-concurrent-htlcs=30", + ) + + return &clnBreezClient{ + name: name, + harness: h, + node: node, + scriptDir: scriptDir, + pluginFilePath: pluginFilePath, + } +} + +func (c *clnBreezClient) Name() string { + return c.name +} + +func (c *clnBreezClient) Harness() *lntest.TestHarness { + return c.harness +} + +func (c *clnBreezClient) Node() lntest.LightningNode { + return c.node +} + +func (c *clnBreezClient) Start() { + c.mtx.Lock() + defer c.mtx.Unlock() + + if !c.isInitialized { + c.initialize() + c.isInitialized = true + } + + c.node.Start() +} + +func (c *clnBreezClient) initialize() error { + var cleanups []*lntest.Cleanup + + pythonFilePath := filepath.Join(c.scriptDir, "zero_conf_plugin.py") + pythonFile, err := os.OpenFile(pythonFilePath, os.O_CREATE|os.O_WRONLY, 0755) + if err != nil { + return fmt.Errorf("failed to create python file '%s': %v", pythonFilePath, err) + } + cleanups = append(cleanups, &lntest.Cleanup{ + Name: fmt.Sprintf("%s: python file", c.name), + Fn: pythonFile.Close, + }) + + pythonWriter := bufio.NewWriter(pythonFile) + _, err = pythonWriter.WriteString(pluginContent) + if err != nil { + lntest.PerformCleanup(cleanups) + return fmt.Errorf("failed to write content to python file '%s': %v", pythonFilePath, err) + } + + err = pythonWriter.Flush() + if err != nil { + lntest.PerformCleanup(cleanups) + return fmt.Errorf("failed to flush python file '%s': %v", pythonFilePath, err) + } + + pluginFile, err := os.OpenFile(c.pluginFilePath, os.O_CREATE|os.O_WRONLY, 0755) + if err != nil { + lntest.PerformCleanup(cleanups) + return fmt.Errorf("failed to create plugin file '%s': %v", c.pluginFilePath, err) + } + cleanups = append(cleanups, &lntest.Cleanup{ + Name: fmt.Sprintf("%s: python file", c.name), + Fn: pluginFile.Close, + }) + + pluginWriter := bufio.NewWriter(pluginFile) + venvDir := filepath.Join(c.scriptDir, "venv") + activatePath := filepath.Join(venvDir, "bin", "activate") + _, err = pluginWriter.WriteString(fmt.Sprintf(pluginStartupContent, venvDir, activatePath, pythonFilePath)) + if err != nil { + lntest.PerformCleanup(cleanups) + return fmt.Errorf("failed to write content to plugin file '%s': %v", c.pluginFilePath, err) + } + + err = pluginWriter.Flush() + if err != nil { + lntest.PerformCleanup(cleanups) + return fmt.Errorf("failed to flush plugin file '%s': %v", c.pluginFilePath, err) + } + lntest.PerformCleanup(cleanups) + return nil +} + +func (c *clnBreezClient) Stop() error { + return c.node.Stop() +} diff --git a/itest/cln_lspd_node.go b/itest/cln_lspd_node.go new file mode 100644 index 0000000..8290fdb --- /dev/null +++ b/itest/cln_lspd_node.go @@ -0,0 +1,133 @@ +package itest + +import ( + "fmt" + "sync" + + "github.com/breez/lntest" + lspd "github.com/breez/lspd/rpc" + "github.com/btcsuite/btcd/btcec/v2" + ecies "github.com/ecies/go/v2" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" +) + +type ClnLspNode struct { + harness *lntest.TestHarness + lightningNode *lntest.ClnNode + lspBase *lspBase + runtime *clnLspNodeRuntime + isInitialized bool + mtx sync.Mutex +} + +type clnLspNodeRuntime struct { + rpc lspd.ChannelOpenerClient + cleanups []*lntest.Cleanup +} + +func NewClnLspdNode(h *lntest.TestHarness, m *lntest.Miner, name string) LspNode { + lspbase, err := newLspd(h, name, "RUN_CLN=true") + if err != nil { + h.T.Fatalf("failed to initialize lspd") + } + + args := []string{ + fmt.Sprintf("--plugin=%s", lspbase.scriptFilePath), + fmt.Sprintf("--fee-base=%d", lspBaseFeeMsat), + fmt.Sprintf("--fee-per-satoshi=%d", lspFeeRatePpm), + fmt.Sprintf("--cltv-delta=%d", lspCltvDelta), + "--max-concurrent-htlcs=30", + "--dev-allowdustreserve=true", + } + lightningNode := lntest.NewClnNode(h, m, name, args...) + + lspNode := &ClnLspNode{ + harness: h, + lightningNode: lightningNode, + lspBase: lspbase, + } + + h.AddStoppable(lspNode) + return lspNode +} + +func (c *ClnLspNode) Start() { + c.mtx.Lock() + defer c.mtx.Unlock() + + var cleanups []*lntest.Cleanup + if !c.isInitialized { + err := c.lspBase.Initialize() + if err != nil { + c.harness.T.Fatalf("failed to initialize lsp: %v", err) + } + c.isInitialized = true + cleanups = append(cleanups, &lntest.Cleanup{ + Name: fmt.Sprintf("%s: lsp base", c.lspBase.name), + Fn: c.lspBase.Stop, + }) + } + + c.lightningNode.Start() + cleanups = append(cleanups, &lntest.Cleanup{ + Name: fmt.Sprintf("%s: lightning node", c.lspBase.name), + Fn: c.lightningNode.Stop, + }) + conn, err := grpc.Dial( + c.lspBase.grpcAddress, + grpc.WithTransportCredentials(insecure.NewCredentials()), + grpc.WithPerRPCCredentials(&token{token: "hello"}), + ) + if err != nil { + lntest.PerformCleanup(cleanups) + c.harness.T.Fatalf("%s: failed to create grpc connection: %v", c.lspBase.name, err) + } + + client := lspd.NewChannelOpenerClient(conn) + c.runtime = &clnLspNodeRuntime{ + rpc: client, + cleanups: cleanups, + } +} + +func (c *ClnLspNode) Stop() error { + c.mtx.Lock() + defer c.mtx.Unlock() + + if c.runtime == nil { + return nil + } + + lntest.PerformCleanup(c.runtime.cleanups) + c.runtime = nil + return nil +} + +func (c *ClnLspNode) Harness() *lntest.TestHarness { + return c.harness +} + +func (c *ClnLspNode) PublicKey() *btcec.PublicKey { + return c.lspBase.pubkey +} + +func (c *ClnLspNode) EciesPublicKey() *ecies.PublicKey { + return c.lspBase.eciesPubkey +} + +func (c *ClnLspNode) Rpc() lspd.ChannelOpenerClient { + return c.runtime.rpc +} + +func (l *ClnLspNode) NodeId() []byte { + return l.lightningNode.NodeId() +} + +func (l *ClnLspNode) LightningNode() lntest.LightningNode { + return l.lightningNode +} + +func (l *ClnLspNode) SupportsChargingFees() bool { + return false +} diff --git a/itest/intercept_zero_conf_test.go b/itest/intercept_zero_conf_test.go index f5d5ab5..9413b6d 100644 --- a/itest/intercept_zero_conf_test.go +++ b/itest/intercept_zero_conf_test.go @@ -13,6 +13,7 @@ var htlcInterceptorDelay = time.Second * 7 func testOpenZeroConfChannelOnReceive(p *testParams) { alice := lntest.NewClnNode(p.h, p.m, "Alice") + alice.Start() alice.Fund(10000000) p.lsp.LightningNode().Fund(10000000) @@ -26,15 +27,16 @@ func testOpenZeroConfChannelOnReceive(p *testParams) { outerAmountMsat := uint64(2100000) innerAmountMsat := calculateInnerAmountMsat(p.lsp, outerAmountMsat) description := "Please pay me" - innerInvoice, outerInvoice := p.BreezClient().GenerateInvoices(generateInvoicesRequest{ - innerAmountMsat: innerAmountMsat, - outerAmountMsat: outerAmountMsat, - description: description, - lsp: p.lsp, - }) + innerInvoice, outerInvoice := GenerateInvoices(p.BreezClient(), + generateInvoicesRequest{ + innerAmountMsat: innerAmountMsat, + outerAmountMsat: outerAmountMsat, + description: description, + lsp: p.lsp, + }) log.Print("Connecting bob to lspd") - p.BreezClient().lightningNode.ConnectPeer(p.lsp.LightningNode()) + p.BreezClient().Node().ConnectPeer(p.lsp.LightningNode()) // NOTE: We pretend to be paying fees to the lsp, but actually we won't. log.Printf("Registering payment with lsp") @@ -42,7 +44,7 @@ func testOpenZeroConfChannelOnReceive(p *testParams) { RegisterPayment(p.lsp, &lspd.PaymentInformation{ PaymentHash: innerInvoice.paymentHash, PaymentSecret: innerInvoice.paymentSecret, - Destination: p.BreezClient().lightningNode.NodeId(), + Destination: p.BreezClient().Node().NodeId(), IncomingAmountMsat: int64(outerAmountMsat), OutgoingAmountMsat: int64(pretendAmount), }) @@ -52,13 +54,13 @@ func testOpenZeroConfChannelOnReceive(p *testParams) { <-time.After(htlcInterceptorDelay) log.Printf("Alice paying") payResp := alice.Pay(outerInvoice.bolt11) - bobInvoice := p.BreezClient().lightningNode.GetInvoice(payResp.PaymentHash) + bobInvoice := p.BreezClient().Node().GetInvoice(payResp.PaymentHash) assert.Equal(p.t, payResp.PaymentPreimage, bobInvoice.PaymentPreimage) assert.Equal(p.t, innerAmountMsat, bobInvoice.AmountReceivedMsat) // Make sure capacity is correct - chans := p.BreezClient().lightningNode.GetChannels() + chans := p.BreezClient().Node().GetChannels() assert.Len(p.t, chans, 1) c := chans[0] AssertChannelCapacity(p.t, outerAmountMsat, c.CapacityMsat) @@ -66,7 +68,7 @@ func testOpenZeroConfChannelOnReceive(p *testParams) { func testOpenZeroConfSingleHtlc(p *testParams) { alice := lntest.NewClnNode(p.h, p.m, "Alice") - + alice.Start() alice.Fund(10000000) p.lsp.LightningNode().Fund(10000000) @@ -80,15 +82,16 @@ func testOpenZeroConfSingleHtlc(p *testParams) { outerAmountMsat := uint64(2100000) innerAmountMsat := calculateInnerAmountMsat(p.lsp, outerAmountMsat) description := "Please pay me" - innerInvoice, outerInvoice := p.BreezClient().GenerateInvoices(generateInvoicesRequest{ - innerAmountMsat: innerAmountMsat, - outerAmountMsat: outerAmountMsat, - description: description, - lsp: p.lsp, - }) + innerInvoice, outerInvoice := GenerateInvoices(p.BreezClient(), + generateInvoicesRequest{ + innerAmountMsat: innerAmountMsat, + outerAmountMsat: outerAmountMsat, + description: description, + lsp: p.lsp, + }) log.Print("Connecting bob to lspd") - p.BreezClient().lightningNode.ConnectPeer(p.lsp.LightningNode()) + p.BreezClient().Node().ConnectPeer(p.lsp.LightningNode()) // NOTE: We pretend to be paying fees to the lsp, but actually we won't. log.Printf("Registering payment with lsp") @@ -96,7 +99,7 @@ func testOpenZeroConfSingleHtlc(p *testParams) { RegisterPayment(p.lsp, &lspd.PaymentInformation{ PaymentHash: innerInvoice.paymentHash, PaymentSecret: innerInvoice.paymentSecret, - Destination: p.BreezClient().lightningNode.NodeId(), + Destination: p.BreezClient().Node().NodeId(), IncomingAmountMsat: int64(outerAmountMsat), OutgoingAmountMsat: int64(pretendAmount), }) @@ -105,15 +108,15 @@ func testOpenZeroConfSingleHtlc(p *testParams) { log.Printf("Waiting %v to allow htlc interceptor to activate.", htlcInterceptorDelay) <-time.After(htlcInterceptorDelay) log.Printf("Alice paying") - route := constructRoute(p.lsp.LightningNode(), p.BreezClient().lightningNode, channelId, lntest.NewShortChanIDFromString("1x0x0"), outerAmountMsat) + route := constructRoute(p.lsp.LightningNode(), p.BreezClient().Node(), channelId, lntest.NewShortChanIDFromString("1x0x0"), outerAmountMsat) payResp := alice.PayViaRoute(outerAmountMsat, outerInvoice.paymentHash, outerInvoice.paymentSecret, route) - bobInvoice := p.BreezClient().lightningNode.GetInvoice(payResp.PaymentHash) + bobInvoice := p.BreezClient().Node().GetInvoice(payResp.PaymentHash) assert.Equal(p.t, payResp.PaymentPreimage, bobInvoice.PaymentPreimage) assert.Equal(p.t, innerAmountMsat, bobInvoice.AmountReceivedMsat) // Make sure capacity is correct - chans := p.BreezClient().lightningNode.GetChannels() + chans := p.BreezClient().Node().GetChannels() assert.Len(p.t, chans, 1) c := chans[0] AssertChannelCapacity(p.t, outerAmountMsat, c.CapacityMsat) diff --git a/itest/lnd_breez_client.go b/itest/lnd_breez_client.go new file mode 100644 index 0000000..6da835a --- /dev/null +++ b/itest/lnd_breez_client.go @@ -0,0 +1,108 @@ +package itest + +import ( + "context" + "flag" + "sync" + + "github.com/breez/lntest" + "github.com/breez/lntest/lnd" + "github.com/lightningnetwork/lnd/lnwire" +) + +var lndMobileExecutable = flag.String( + "lndmobileexec", "", "full path to lnd mobile binary", +) + +type lndBreezClient struct { + name string + harness *lntest.TestHarness + node *lntest.LndNode + cancel context.CancelFunc + mtx sync.Mutex +} + +func newLndBreezClient(h *lntest.TestHarness, m *lntest.Miner, name string) BreezClient { + lnd := lntest.NewLndNodeFromBinary(h, m, name, *lndMobileExecutable, + "--protocol.zero-conf", + "--protocol.option-scid-alias", + "--bitcoin.defaultchanconfs=0", + ) + + c := &lndBreezClient{ + name: name, + harness: h, + node: lnd, + } + h.AddStoppable(c) + return c +} + +func (c *lndBreezClient) Name() string { + return c.name +} + +func (c *lndBreezClient) Harness() *lntest.TestHarness { + return c.harness +} + +func (c *lndBreezClient) Node() lntest.LightningNode { + return c.node +} + +func (c *lndBreezClient) Start() { + c.mtx.Lock() + defer c.mtx.Unlock() + + if c.node.IsStarted() { + return + } + + c.node.Start() + + ctx, cancel := context.WithCancel(c.harness.Ctx) + c.cancel = cancel + go c.startChannelAcceptor(ctx) +} + +func (c *lndBreezClient) Stop() error { + c.mtx.Lock() + defer c.mtx.Unlock() + + // Stop the channel acceptor + if c.cancel != nil { + c.cancel() + c.cancel = nil + } + + return c.node.Stop() +} + +func (c *lndBreezClient) startChannelAcceptor(ctx context.Context) error { + client, err := c.node.LightningClient().ChannelAcceptor(ctx) + if err != nil { + c.harness.T.Fatalf("%s: failed to create channel acceptor: %v", c.name, err) + } + + for { + request, err := client.Recv() + if err != nil { + return err + } + + private := request.ChannelFlags&uint32(lnwire.FFAnnounceChannel) == 0 + resp := &lnd.ChannelAcceptResponse{ + PendingChanId: request.PendingChanId, + Accept: private, + } + if request.WantsZeroConf { + resp.MinAcceptDepth = 0 + resp.ZeroConf = true + } + + err = client.Send(resp) + if err != nil { + c.harness.T.Fatalf("%s: failed to send acceptor response: %v", c.name, err) + } + } +} diff --git a/itest/lnd_lspd_node.go b/itest/lnd_lspd_node.go new file mode 100644 index 0000000..7fa3385 --- /dev/null +++ b/itest/lnd_lspd_node.go @@ -0,0 +1,230 @@ +package itest + +import ( + "fmt" + "log" + "os" + "os/exec" + "path/filepath" + "runtime" + "strings" + "sync" + + "github.com/breez/lntest" + lspd "github.com/breez/lspd/rpc" + "github.com/btcsuite/btcd/btcec/v2" + ecies "github.com/ecies/go/v2" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" +) + +type LndLspNode struct { + harness *lntest.TestHarness + lightningNode *lntest.LndNode + logFilePath string + isInitialized bool + lspBase *lspBase + runtime *lndLspNodeRuntime + mtx sync.Mutex +} + +type lndLspNodeRuntime struct { + logFile *os.File + cmd *exec.Cmd + rpc lspd.ChannelOpenerClient + cleanups []*lntest.Cleanup +} + +func NewLndLspdNode(h *lntest.TestHarness, m *lntest.Miner, name string) LspNode { + args := []string{ + "--protocol.zero-conf", + "--protocol.option-scid-alias", + "--requireinterceptor", + "--bitcoin.defaultchanconfs=0", + fmt.Sprintf("--bitcoin.chanreservescript=\"0 if (chanAmt != %d) else chanAmt/100\"", publicChanAmount), + fmt.Sprintf("--bitcoin.basefee=%d", lspBaseFeeMsat), + fmt.Sprintf("--bitcoin.feerate=%d", lspFeeRatePpm), + fmt.Sprintf("--bitcoin.timelockdelta=%d", lspCltvDelta), + } + + lightningNode := lntest.NewLndNode(h, m, name, args...) + tlsCert := strings.Replace(string(lightningNode.TlsCert()), "\n", "\\n", -1) + lspBase, err := newLspd(h, name, + "RUN_LND=true", + fmt.Sprintf("LND_CERT=\"%s\"", tlsCert), + fmt.Sprintf("LND_ADDRESS=%s", lightningNode.GrpcHost()), + fmt.Sprintf("LND_MACAROON_HEX=%x", lightningNode.Macaroon()), + ) + if err != nil { + h.T.Fatalf("failed to initialize lspd") + } + scriptDir := filepath.Dir(lspBase.scriptFilePath) + logFilePath := filepath.Join(scriptDir, "lspd.log") + h.RegisterLogfile(logFilePath, fmt.Sprintf("lspd-%s", name)) + + lspNode := &LndLspNode{ + harness: h, + lightningNode: lightningNode, + logFilePath: logFilePath, + lspBase: lspBase, + } + + h.AddStoppable(lspNode) + return lspNode +} + +func (c *LndLspNode) Start() { + c.mtx.Lock() + defer c.mtx.Unlock() + + var cleanups []*lntest.Cleanup + wasInitialized := c.isInitialized + if !c.isInitialized { + err := c.lspBase.Initialize() + if err != nil { + c.harness.T.Fatalf("failed to initialize lsp: %v", err) + } + c.isInitialized = true + cleanups = append(cleanups, &lntest.Cleanup{ + Name: fmt.Sprintf("%s: lsp base", c.lspBase.name), + Fn: c.lspBase.Stop, + }) + } + + c.lightningNode.Start() + cleanups = append(cleanups, &lntest.Cleanup{ + Name: fmt.Sprintf("%s: lsp lightning node", c.lspBase.name), + Fn: c.lightningNode.Stop, + }) + + if !wasInitialized { + scriptFile, err := os.ReadFile(c.lspBase.scriptFilePath) + if err != nil { + lntest.PerformCleanup(cleanups) + c.harness.T.Fatalf("failed to open scriptfile '%s': %v", c.lspBase.scriptFilePath, err) + } + + err = os.Remove(c.lspBase.scriptFilePath) + if err != nil { + lntest.PerformCleanup(cleanups) + c.harness.T.Fatalf("failed to remove scriptfile '%s': %v", c.lspBase.scriptFilePath, err) + } + + split := strings.Split(string(scriptFile), "\n") + for i, s := range split { + if strings.HasPrefix(s, "export LND_CERT") { + tlsCert := strings.Replace(string(c.lightningNode.TlsCert()), "\n", "\\n", -1) + split[i] = fmt.Sprintf("export LND_CERT=\"%s\"", tlsCert) + } + + if strings.HasPrefix(s, "export LND_MACAROON_HEX") { + split[i] = fmt.Sprintf("export LND_MACAROON_HEX=%x", c.lightningNode.Macaroon()) + } + } + newContent := strings.Join(split, "\n") + err = os.WriteFile(c.lspBase.scriptFilePath, []byte(newContent), 0755) + if err != nil { + lntest.PerformCleanup(cleanups) + c.harness.T.Fatalf("failed to rewrite scriptfile '%s': %v", c.lspBase.scriptFilePath, err) + } + } + + cmd := exec.CommandContext(c.harness.Ctx, c.lspBase.scriptFilePath) + logFile, err := os.Create(c.logFilePath) + if err != nil { + lntest.PerformCleanup(cleanups) + c.harness.T.Fatalf("failed create lsp logfile: %v", err) + } + cleanups = append(cleanups, &lntest.Cleanup{ + Name: fmt.Sprintf("%s: logfile", c.lspBase.name), + Fn: logFile.Close, + }) + + cmd.Stdout = logFile + cmd.Stderr = logFile + + log.Printf("%s: starting lspd %s", c.lspBase.name, c.lspBase.scriptFilePath) + err = cmd.Start() + if err != nil { + lntest.PerformCleanup(cleanups) + c.harness.T.Fatalf("failed to start lspd: %v", err) + } + cleanups = append(cleanups, &lntest.Cleanup{ + Name: fmt.Sprintf("%s: cmd", c.lspBase.name), + Fn: func() error { + proc := cmd.Process + if proc != nil { + if runtime.GOOS == "windows" { + return proc.Signal(os.Kill) + } + + return proc.Signal(os.Interrupt) + } + + return nil + }, + }) + + conn, err := grpc.Dial( + c.lspBase.grpcAddress, + grpc.WithTransportCredentials(insecure.NewCredentials()), + grpc.WithPerRPCCredentials(&token{token: "hello"}), + ) + if err != nil { + lntest.PerformCleanup(cleanups) + c.harness.T.Fatalf("failed to create grpc connection: %v", err) + } + cleanups = append(cleanups, &lntest.Cleanup{ + Name: fmt.Sprintf("%s: grpc conn", c.lspBase.name), + Fn: conn.Close, + }) + + client := lspd.NewChannelOpenerClient(conn) + c.runtime = &lndLspNodeRuntime{ + logFile: logFile, + cmd: cmd, + rpc: client, + cleanups: cleanups, + } +} + +func (c *LndLspNode) Stop() error { + c.mtx.Lock() + defer c.mtx.Unlock() + + if c.runtime == nil { + return nil + } + + lntest.PerformCleanup(c.runtime.cleanups) + c.runtime = nil + return nil +} + +func (c *LndLspNode) Harness() *lntest.TestHarness { + return c.harness +} + +func (c *LndLspNode) PublicKey() *btcec.PublicKey { + return c.lspBase.pubkey +} + +func (c *LndLspNode) EciesPublicKey() *ecies.PublicKey { + return c.lspBase.eciesPubkey +} + +func (c *LndLspNode) Rpc() lspd.ChannelOpenerClient { + return c.runtime.rpc +} + +func (l *LndLspNode) SupportsChargingFees() bool { + return true +} + +func (l *LndLspNode) NodeId() []byte { + return l.lightningNode.NodeId() +} + +func (l *LndLspNode) LightningNode() lntest.LightningNode { + return l.lightningNode +} diff --git a/itest/lspd_node.go b/itest/lspd_node.go index e845d5b..c4601bc 100644 --- a/itest/lspd_node.go +++ b/itest/lspd_node.go @@ -9,7 +9,6 @@ import ( "os" "os/exec" "path/filepath" - "strings" "github.com/breez/lntest" lspd "github.com/breez/lspd/rpc" @@ -17,8 +16,6 @@ import ( "github.com/decred/dcrd/dcrec/secp256k1/v4" ecies "github.com/ecies/go/v2" "github.com/golang/protobuf/proto" - "google.golang.org/grpc" - "google.golang.org/grpc/credentials/insecure" ) var ( @@ -37,6 +34,8 @@ var ( ) type LspNode interface { + Start() + Stop() error Harness() *lntest.TestHarness PublicKey() *btcec.PublicKey EciesPublicKey() *ecies.PublicKey @@ -46,228 +45,43 @@ type LspNode interface { SupportsChargingFees() bool } -type ClnLspNode struct { +type lspBase struct { harness *lntest.TestHarness - lightningNode *lntest.ClnNode - rpc lspd.ChannelOpenerClient - publicKey btcec.PublicKey - eciesPublicKey ecies.PublicKey + name string + binary string + env []string + scriptFilePath string + grpcAddress string + pubkey *secp256k1.PublicKey + eciesPubkey *ecies.PublicKey postgresBackend *PostgresContainer } -func (c *ClnLspNode) Harness() *lntest.TestHarness { - return c.harness -} - -func (c *ClnLspNode) PublicKey() *btcec.PublicKey { - return &c.publicKey -} - -func (c *ClnLspNode) EciesPublicKey() *ecies.PublicKey { - return &c.eciesPublicKey -} - -func (c *ClnLspNode) Rpc() lspd.ChannelOpenerClient { - return c.rpc -} - -func (l *ClnLspNode) TearDown() error { - // NOTE: The lightningnode will be torn down on its own. - return l.postgresBackend.Shutdown(context.Background()) -} - -func (l *ClnLspNode) Cleanup() error { - return l.postgresBackend.Cleanup(context.Background()) -} - -func (l *ClnLspNode) NodeId() []byte { - return l.lightningNode.NodeId() -} - -func (l *ClnLspNode) LightningNode() lntest.LightningNode { - return l.lightningNode -} - -func (l *ClnLspNode) SupportsChargingFees() bool { - return false -} - -type LndLspNode struct { - harness *lntest.TestHarness - lightningNode *lntest.LndNode - rpc lspd.ChannelOpenerClient - publicKey btcec.PublicKey - eciesPublicKey ecies.PublicKey - postgresBackend *PostgresContainer - logFile *os.File - lspdCmd *exec.Cmd -} - -func (c *LndLspNode) Harness() *lntest.TestHarness { - return c.harness -} - -func (c *LndLspNode) PublicKey() *btcec.PublicKey { - return &c.publicKey -} - -func (c *LndLspNode) EciesPublicKey() *ecies.PublicKey { - return &c.eciesPublicKey -} - -func (c *LndLspNode) Rpc() lspd.ChannelOpenerClient { - return c.rpc -} -func (l *LndLspNode) SupportsChargingFees() bool { - return true -} - -func (l *LndLspNode) TearDown() error { - // NOTE: The lightningnode will be torn down on its own. - if l.lspdCmd != nil && l.lspdCmd.Process != nil { - err := l.lspdCmd.Process.Kill() - if err != nil { - log.Printf("error stopping lspd process: %v", err) - } - } - - if l.logFile != nil { - err := l.logFile.Close() - if err != nil { - log.Printf("error closing logfile: %v", err) - } - } - - return l.postgresBackend.Shutdown(context.Background()) -} - -func (l *LndLspNode) Cleanup() error { - return l.postgresBackend.Cleanup(context.Background()) -} - -func (l *LndLspNode) NodeId() []byte { - return l.lightningNode.NodeId() -} - -func (l *LndLspNode) LightningNode() lntest.LightningNode { - return l.lightningNode -} - -func NewClnLspdNode(h *lntest.TestHarness, m *lntest.Miner, name string) LspNode { - scriptFilePath, grpcAddress, publ, eciesPubl, postgresBackend := setupLspd(h, name, "RUN_CLN=true") - args := []string{ - fmt.Sprintf("--plugin=%s", scriptFilePath), - fmt.Sprintf("--fee-base=%d", lspBaseFeeMsat), - fmt.Sprintf("--fee-per-satoshi=%d", lspFeeRatePpm), - fmt.Sprintf("--cltv-delta=%d", lspCltvDelta), - "--max-concurrent-htlcs=30", - "--dev-allowdustreserve=true", - } - - lightningNode := lntest.NewClnNode(h, m, name, args...) - - conn, err := grpc.Dial( - grpcAddress, - grpc.WithTransportCredentials(insecure.NewCredentials()), - grpc.WithPerRPCCredentials(&token{token: "hello"}), - ) - lntest.CheckError(h.T, err) - - client := lspd.NewChannelOpenerClient(conn) - - lspNode := &ClnLspNode{ - harness: h, - lightningNode: lightningNode, - rpc: client, - publicKey: *publ, - eciesPublicKey: *eciesPubl, - postgresBackend: postgresBackend, - } - - h.AddStoppable(lspNode) - h.AddCleanable(lspNode) - return lspNode -} - -func NewLndLspdNode(h *lntest.TestHarness, m *lntest.Miner, name string) LspNode { - args := []string{ - "--protocol.zero-conf", - "--protocol.option-scid-alias", - "--requireinterceptor", - "--bitcoin.defaultchanconfs=0", - fmt.Sprintf("--bitcoin.chanreservescript=\"0 if (chanAmt != %d) else chanAmt/100\"", publicChanAmount), - fmt.Sprintf("--bitcoin.basefee=%d", lspBaseFeeMsat), - fmt.Sprintf("--bitcoin.feerate=%d", lspFeeRatePpm), - fmt.Sprintf("--bitcoin.timelockdelta=%d", lspCltvDelta), - } - - lightningNode := lntest.NewLndNode(h, m, name, args...) - tlsCert := strings.Replace(string(lightningNode.TlsCert()), "\n", "\\n", -1) - scriptFilePath, grpcAddress, publ, eciesPubl, postgresBackend := setupLspd(h, name, - "RUN_LND=true", - fmt.Sprintf("LND_CERT=\"%s\"", tlsCert), - fmt.Sprintf("LND_ADDRESS=%s", lightningNode.GrpcHost()), - fmt.Sprintf("LND_MACAROON_HEX=%x", lightningNode.Macaroon()), - ) - scriptDir := filepath.Dir(scriptFilePath) - logFilePath := filepath.Join(scriptDir, "lspd.log") - h.RegisterLogfile(logFilePath, fmt.Sprintf("lspd-%s", name)) - - lspdCmd := exec.CommandContext(h.Ctx, scriptFilePath) - logFile, err := os.Create(logFilePath) - lntest.CheckError(h.T, err) - - lspdCmd.Stdout = logFile - lspdCmd.Stderr = logFile - - log.Printf("%s: starting lspd %s", name, scriptFilePath) - err = lspdCmd.Start() - lntest.CheckError(h.T, err) - - conn, err := grpc.Dial( - grpcAddress, - grpc.WithTransportCredentials(insecure.NewCredentials()), - grpc.WithPerRPCCredentials(&token{token: "hello"}), - ) - lntest.CheckError(h.T, err) - - client := lspd.NewChannelOpenerClient(conn) - - lspNode := &LndLspNode{ - harness: h, - lightningNode: lightningNode, - rpc: client, - publicKey: *publ, - eciesPublicKey: *eciesPubl, - postgresBackend: postgresBackend, - logFile: logFile, - lspdCmd: lspdCmd, - } - - h.AddStoppable(lspNode) - h.AddCleanable(lspNode) - return lspNode -} - -func setupLspd(h *lntest.TestHarness, name string, envExt ...string) (string, string, *secp256k1.PublicKey, *ecies.PublicKey, *PostgresContainer) { +func newLspd(h *lntest.TestHarness, name string, envExt ...string) (*lspBase, error) { scriptDir := h.GetDirectory(fmt.Sprintf("lspd-%s", name)) log.Printf("%s: Creating LSPD in dir %s", name, scriptDir) - migrationsDir, err := getMigrationsDir() - lntest.CheckError(h.T, err) pgLogfile := filepath.Join(scriptDir, "postgres.log") h.RegisterLogfile(pgLogfile, fmt.Sprintf("%s-postgres", name)) - postgresBackend := StartPostgresContainer(h.T, h.Ctx, pgLogfile) - postgresBackend.RunMigrations(h.T, h.Ctx, migrationsDir) + postgresBackend, err := NewPostgresContainer(pgLogfile) + if err != nil { + return nil, err + } lspdBinary, err := getLspdBinary() - lntest.CheckError(h.T, err) + if err != nil { + return nil, err + } lspdPort, err := lntest.GetPort() - lntest.CheckError(h.T, err) + if err != nil { + return nil, err + } lspdPrivateKeyBytes, err := GenerateRandomBytes(32) - lntest.CheckError(h.T, err) + if err != nil { + return nil, err + } _, publ := btcec.PrivKeyFromBytes(lspdPrivateKeyBytes) eciesPubl := ecies.NewPrivateKeyFromBytes(lspdPrivateKeyBytes).PublicKey @@ -286,27 +100,91 @@ func setupLspd(h *lntest.TestHarness, name string, envExt ...string) (string, st env = append(env, envExt...) scriptFilePath := filepath.Join(scriptDir, "start-lspd.sh") - log.Printf("%s: Creating lspd startup script at %s", name, scriptFilePath) - scriptFile, err := os.OpenFile(scriptFilePath, os.O_CREATE|os.O_WRONLY, 0755) - lntest.CheckError(h.T, err) - writer := bufio.NewWriter(scriptFile) - _, err = writer.WriteString("#!/bin/bash\n") - lntest.CheckError(h.T, err) + l := &lspBase{ + harness: h, + name: name, + env: env, + binary: lspdBinary, + scriptFilePath: scriptFilePath, + grpcAddress: grpcAddress, + pubkey: publ, + eciesPubkey: eciesPubl, + postgresBackend: postgresBackend, + } + h.AddStoppable(l) + h.AddCleanable(l) + return l, nil +} - for _, str := range env { - _, err = writer.WriteString("export " + str + "\n") - lntest.CheckError(h.T, err) +func (l *lspBase) Stop() error { + return l.postgresBackend.Stop(context.Background()) +} + +func (l *lspBase) Cleanup() error { + return l.postgresBackend.Cleanup(context.Background()) +} + +func (l *lspBase) Initialize() error { + var cleanups []*lntest.Cleanup + migrationsDir, err := getMigrationsDir() + if err != nil { + return err } - _, err = writer.WriteString(lspdBinary + "\n") - lntest.CheckError(h.T, err) + err = l.postgresBackend.Start(l.harness.Ctx) + if err != nil { + return err + } + + cleanups = append(cleanups, &lntest.Cleanup{ + Name: fmt.Sprintf("%s: postgres container", l.name), + Fn: func() error { + return l.postgresBackend.Stop(context.Background()) + }, + }) + err = l.postgresBackend.RunMigrations(l.harness.Ctx, migrationsDir) + if err != nil { + lntest.PerformCleanup(cleanups) + return err + } + + log.Printf("%s: Creating lspd startup script at %s", l.name, l.scriptFilePath) + scriptFile, err := os.OpenFile(l.scriptFilePath, os.O_CREATE|os.O_WRONLY, 0755) + if err != nil { + lntest.PerformCleanup(cleanups) + return err + } + + defer scriptFile.Close() + writer := bufio.NewWriter(scriptFile) + _, err = writer.WriteString("#!/bin/bash\n") + if err != nil { + lntest.PerformCleanup(cleanups) + return err + } + + for _, str := range l.env { + _, err = writer.WriteString("export " + str + "\n") + if err != nil { + lntest.PerformCleanup(cleanups) + return err + } + } + + _, err = writer.WriteString(l.binary + "\n") + if err != nil { + lntest.PerformCleanup(cleanups) + return err + } err = writer.Flush() - lntest.CheckError(h.T, err) - scriptFile.Close() + if err != nil { + lntest.PerformCleanup(cleanups) + return err + } - return scriptFilePath, grpcAddress, publ, eciesPubl, postgresBackend + return nil } func RegisterPayment(l LspNode, paymentInfo *lspd.PaymentInformation) { diff --git a/itest/lspd_test.go b/itest/lspd_test.go index 6ae6c37..14b4ff5 100644 --- a/itest/lspd_test.go +++ b/itest/lspd_test.go @@ -3,6 +3,7 @@ package itest import ( "fmt" "log" + "sync" "testing" "time" @@ -13,16 +14,16 @@ var defaultTimeout time.Duration = time.Second * 120 func TestLspd(t *testing.T) { testCases := allTestCases - runTests(t, testCases, "LND-lspd", func(h *lntest.TestHarness, m *lntest.Miner) (LspNode, *breezClient) { + runTests(t, testCases, "LND-lspd", func(h *lntest.TestHarness, m *lntest.Miner) (LspNode, BreezClient) { return NewLndLspdNode(h, m, "lsp"), newLndBreezClient(h, m, "breez-client") }) - runTests(t, testCases, "CLN-lspd", func(h *lntest.TestHarness, m *lntest.Miner) (LspNode, *breezClient) { + runTests(t, testCases, "CLN-lspd", func(h *lntest.TestHarness, m *lntest.Miner) (LspNode, BreezClient) { return NewClnLspdNode(h, m, "lsp"), newClnBreezClient(h, m, "breez-client") }) } -func runTests(t *testing.T, testCases []*testCase, prefix string, nodesFunc func(h *lntest.TestHarness, m *lntest.Miner) (LspNode, *breezClient)) { +func runTests(t *testing.T, testCases []*testCase, prefix string, nodesFunc func(h *lntest.TestHarness, m *lntest.Miner) (LspNode, BreezClient)) { for _, testCase := range testCases { testCase := testCase t.Run(fmt.Sprintf("%s: %s", prefix, testCase.name), func(t *testing.T) { @@ -31,7 +32,7 @@ func runTests(t *testing.T, testCases []*testCase, prefix string, nodesFunc func } } -func runTest(t *testing.T, testCase *testCase, prefix string, nodesFunc func(h *lntest.TestHarness, m *lntest.Miner) (LspNode, *breezClient)) { +func runTest(t *testing.T, testCase *testCase, prefix string, nodesFunc func(h *lntest.TestHarness, m *lntest.Miner) (LspNode, BreezClient)) { log.Printf("%s: Running test case '%s'", prefix, testCase.name) var dd time.Duration to := testCase.timeout @@ -47,8 +48,21 @@ func runTest(t *testing.T, testCase *testCase, prefix string, nodesFunc func(h * log.Printf("Creating miner") miner := lntest.NewMiner(h) + miner.Start() log.Printf("Creating lsp") lsp, c := nodesFunc(h, miner) + var wg sync.WaitGroup + wg.Add(2) + go func() { + lsp.Start() + wg.Done() + }() + + go func() { + c.Start() + wg.Done() + }() + wg.Wait() log.Printf("Run testcase") testCase.test(&testParams{ t: t, diff --git a/itest/postgres.go b/itest/postgres.go index bc3a89d..a8c4f54 100644 --- a/itest/postgres.go +++ b/itest/postgres.go @@ -3,7 +3,6 @@ package itest import ( "context" "encoding/binary" - "errors" "fmt" "io" "log" @@ -11,7 +10,7 @@ import ( "path/filepath" "sort" "strconv" - "testing" + "sync" "time" "github.com/breez/lntest" @@ -23,33 +22,112 @@ import ( ) type PostgresContainer struct { - id string - password string - port uint32 - cli *client.Client + id string + password string + port uint32 + cli *client.Client + logfile string + isInitialized bool + isStarted bool + mtx sync.Mutex } -func StartPostgresContainer(t *testing.T, ctx context.Context, logfile string) *PostgresContainer { - cli, err := client.NewClientWithOpts(client.FromEnv) - lntest.CheckError(t, err) - - image := "postgres:15" - _, _, err = cli.ImageInspectWithRaw(ctx, image) +func NewPostgresContainer(logfile string) (*PostgresContainer, error) { + port, err := lntest.GetPort() if err != nil { - if !client.IsErrNotFound(err) { - lntest.CheckError(t, err) - } - - pullReader, err := cli.ImagePull(ctx, image, types.ImagePullOptions{}) - lntest.CheckError(t, err) - _, err = io.Copy(io.Discard, pullReader) - pullReader.Close() - lntest.CheckError(t, err) + return nil, fmt.Errorf("could not get port: %w", err) } - port, err := lntest.GetPort() - lntest.CheckError(t, err) - createResp, err := cli.ContainerCreate(ctx, &container.Config{ + return &PostgresContainer{ + password: "pgpassword", + port: port, + }, nil +} + +func (c *PostgresContainer) Start(ctx context.Context) error { + c.mtx.Lock() + defer c.mtx.Unlock() + + var err error + if c.isStarted { + return nil + } + + c.cli, err = client.NewClientWithOpts(client.FromEnv) + if err != nil { + return fmt.Errorf("could not create docker client: %w", err) + } + + if !c.isInitialized { + err := c.initialize(ctx) + if err != nil { + c.cli.Close() + return err + } + } + + err = c.cli.ContainerStart(ctx, c.id, types.ContainerStartOptions{}) + if err != nil { + c.cli.Close() + return fmt.Errorf("failed to start docker container '%s': %w", c.id, err) + } + c.isStarted = true + +HealthCheck: + for { + inspect, err := c.cli.ContainerInspect(ctx, c.id) + if err != nil { + c.cli.ContainerStop(ctx, c.id, nil) + c.cli.Close() + return fmt.Errorf("failed to inspect container '%s' during healthcheck: %w", c.id, err) + } + + status := inspect.State.Health.Status + switch status { + case "unhealthy": + c.cli.ContainerStop(ctx, c.id, nil) + c.cli.Close() + return fmt.Errorf("container '%s' unhealthy", c.id) + case "healthy": + for { + pgxPool, err := pgxpool.Connect(ctx, c.ConnectionString()) + if err == nil { + pgxPool.Close() + break HealthCheck + } + + <-time.After(50 * time.Millisecond) + } + default: + <-time.After(200 * time.Millisecond) + } + } + + go c.monitorLogs(ctx) + return nil +} + +func (c *PostgresContainer) initialize(ctx context.Context) error { + image := "postgres:15" + _, _, err := c.cli.ImageInspectWithRaw(ctx, image) + if err != nil { + if !client.IsErrNotFound(err) { + return fmt.Errorf("could not find docker image '%s': %w", image, err) + } + + pullReader, err := c.cli.ImagePull(ctx, image, types.ImagePullOptions{}) + if err != nil { + return fmt.Errorf("failed to pull docker image '%s': %w", image, err) + } + defer pullReader.Close() + + _, err = io.Copy(io.Discard, pullReader) + if err != nil { + return fmt.Errorf("failed to download docker image '%s': %w", image, err) + } + } + + createResp, err := c.cli.ContainerCreate(ctx, &container.Config{ Image: image, Cmd: []string{ "postgres", @@ -70,7 +148,7 @@ func StartPostgresContainer(t *testing.T, ctx context.Context, logfile string) * }, &container.HostConfig{ PortBindings: nat.PortMap{ "5432/tcp": []nat.PortBinding{ - {HostPort: strconv.FormatUint(uint64(port), 10)}, + {HostPort: strconv.FormatUint(uint64(c.port), 10)}, }, }, }, @@ -78,48 +156,45 @@ func StartPostgresContainer(t *testing.T, ctx context.Context, logfile string) * nil, "", ) - lntest.CheckError(t, err) - err = cli.ContainerStart(ctx, createResp.ID, types.ContainerStartOptions{}) - lntest.CheckError(t, err) - - ct := &PostgresContainer{ - id: createResp.ID, - password: "pgpassword", - port: port, - cli: cli, + if err != nil { + return fmt.Errorf("failed to create docker container: %w", err) } -HealthCheck: - for { - inspect, err := cli.ContainerInspect(ctx, createResp.ID) - lntest.CheckError(t, err) - - status := inspect.State.Health.Status - switch status { - case "unhealthy": - lntest.CheckError(t, errors.New("container unhealthy")) - case "healthy": - for { - pgxPool, err := pgxpool.Connect(context.Background(), ct.ConnectionString()) - if err == nil { - pgxPool.Close() - break HealthCheck - } - - <-time.After(50 * time.Millisecond) - } - default: - <-time.After(200 * time.Millisecond) - } - } - - go ct.monitorLogs(logfile) - return ct + c.id = createResp.ID + c.isInitialized = true + return nil } -func (c *PostgresContainer) monitorLogs(logfile string) { - i, err := c.cli.ContainerLogs(context.Background(), c.id, types.ContainerLogsOptions{ +func (c *PostgresContainer) Stop(ctx context.Context) error { + c.mtx.Lock() + defer c.mtx.Unlock() + + if !c.isStarted { + return nil + } + + defer c.cli.Close() + err := c.cli.ContainerStop(ctx, c.id, nil) + c.isStarted = false + return err +} + +func (c *PostgresContainer) Cleanup(ctx context.Context) error { + c.mtx.Lock() + defer c.mtx.Unlock() + cli, err := client.NewClientWithOpts(client.FromEnv) + if err != nil { + return err + } + defer cli.Close() + return cli.ContainerRemove(ctx, c.id, types.ContainerRemoveOptions{ + Force: true, + }) +} + +func (c *PostgresContainer) monitorLogs(ctx context.Context) { + i, err := c.cli.ContainerLogs(ctx, c.id, types.ContainerLogsOptions{ ShowStderr: true, ShowStdout: true, Timestamps: false, @@ -132,7 +207,7 @@ func (c *PostgresContainer) monitorLogs(logfile string) { } defer i.Close() - file, err := os.OpenFile(logfile, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0600) + file, err := os.OpenFile(c.logfile, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0600) if err != nil { log.Printf("Could not create container log file: %v", err) return @@ -162,39 +237,31 @@ func (c *PostgresContainer) ConnectionString() string { return fmt.Sprintf("postgres://postgres:%s@127.0.0.1:%d/postgres", c.password, c.port) } -func (c *PostgresContainer) Shutdown(ctx context.Context) error { - defer c.cli.Close() - timeout := time.Second - err := c.cli.ContainerStop(ctx, c.id, &timeout) - return err -} - -func (c *PostgresContainer) Cleanup(ctx context.Context) error { - cli, err := client.NewClientWithOpts(client.FromEnv) - if err != nil { - return err - } - defer cli.Close() - return cli.ContainerRemove(ctx, c.id, types.ContainerRemoveOptions{ - Force: true, - }) -} - -func (c *PostgresContainer) RunMigrations(t *testing.T, ctx context.Context, migrationDir string) { +func (c *PostgresContainer) RunMigrations(ctx context.Context, migrationDir string) error { filenames, err := filepath.Glob(filepath.Join(migrationDir, "*.up.sql")) - lntest.CheckError(t, err) + if err != nil { + return fmt.Errorf("failed to glob migration files: %w", err) + } sort.Strings(filenames) - pgxPool, err := pgxpool.Connect(context.Background(), c.ConnectionString()) - lntest.CheckError(t, err) + pgxPool, err := pgxpool.Connect(ctx, c.ConnectionString()) + if err != nil { + return fmt.Errorf("failed to connect to postgres: %w", err) + } defer pgxPool.Close() for _, filename := range filenames { data, err := os.ReadFile(filename) - lntest.CheckError(t, err) + if err != nil { + return fmt.Errorf("failed to read migration file '%s': %w", filename, err) + } _, err = pgxPool.Exec(ctx, string(data)) - lntest.CheckError(t, err) + if err != nil { + return fmt.Errorf("failed to execute migration file '%s': %w", filename, err) + } } + + return nil } diff --git a/itest/test_params.go b/itest/test_params.go index 5343940..d67f708 100644 --- a/itest/test_params.go +++ b/itest/test_params.go @@ -10,7 +10,7 @@ type testParams struct { t *testing.T h *lntest.TestHarness m *lntest.Miner - c *breezClient + c BreezClient lsp LspNode } @@ -30,6 +30,6 @@ func (h *testParams) Harness() *lntest.TestHarness { return h.h } -func (h *testParams) BreezClient() *breezClient { +func (h *testParams) BreezClient() BreezClient { return h.c } diff --git a/itest/zero_reserve_test.go b/itest/zero_reserve_test.go index ea6e8d6..f9f7486 100644 --- a/itest/zero_reserve_test.go +++ b/itest/zero_reserve_test.go @@ -11,6 +11,7 @@ import ( func testZeroReserve(p *testParams) { alice := lntest.NewClnNode(p.h, p.m, "Alice") + alice.Start() alice.Fund(10000000) p.lsp.LightningNode().Fund(10000000) @@ -24,15 +25,16 @@ func testZeroReserve(p *testParams) { outerAmountMsat := uint64(2100000) innerAmountMsat := calculateInnerAmountMsat(p.lsp, outerAmountMsat) description := "Please pay me" - innerInvoice, outerInvoice := p.BreezClient().GenerateInvoices(generateInvoicesRequest{ - innerAmountMsat: innerAmountMsat, - outerAmountMsat: outerAmountMsat, - description: description, - lsp: p.lsp, - }) + innerInvoice, outerInvoice := GenerateInvoices(p.BreezClient(), + generateInvoicesRequest{ + innerAmountMsat: innerAmountMsat, + outerAmountMsat: outerAmountMsat, + description: description, + lsp: p.lsp, + }) log.Print("Connecting bob to lspd") - p.BreezClient().lightningNode.ConnectPeer(p.lsp.LightningNode()) + p.BreezClient().Node().ConnectPeer(p.lsp.LightningNode()) // NOTE: We pretend to be paying fees to the lsp, but actually we won't. log.Printf("Registering payment with lsp") @@ -40,7 +42,7 @@ func testZeroReserve(p *testParams) { RegisterPayment(p.lsp, &lspd.PaymentInformation{ PaymentHash: innerInvoice.paymentHash, PaymentSecret: innerInvoice.paymentSecret, - Destination: p.BreezClient().lightningNode.NodeId(), + Destination: p.BreezClient().Node().NodeId(), IncomingAmountMsat: int64(outerAmountMsat), OutgoingAmountMsat: int64(pretendAmount), }) @@ -49,11 +51,11 @@ func testZeroReserve(p *testParams) { log.Printf("Waiting %v to allow htlc interceptor to activate.", htlcInterceptorDelay) <-time.After(htlcInterceptorDelay) log.Printf("Alice paying") - route := constructRoute(p.lsp.LightningNode(), p.BreezClient().lightningNode, channelId, lntest.NewShortChanIDFromString("1x0x0"), outerAmountMsat) + route := constructRoute(p.lsp.LightningNode(), p.BreezClient().Node(), channelId, lntest.NewShortChanIDFromString("1x0x0"), outerAmountMsat) alice.PayViaRoute(outerAmountMsat, outerInvoice.paymentHash, outerInvoice.paymentSecret, route) // Make sure balance is correct - chans := p.BreezClient().lightningNode.GetChannels() + chans := p.BreezClient().Node().GetChannels() assert.Len(p.t, chans, 1) c := chans[0] From 649086dd0ad33239fa3af766a0e7831d102a6663 Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Fri, 16 Dec 2022 21:03:20 +0100 Subject: [PATCH 110/214] add test for offline node + fix cln failurecode --- cln_interceptor.go | 22 ++++++++--- go.mod | 6 +-- intercept.go | 28 +++++++++----- itest/bob_offline_test.go | 61 +++++++++++++++++++++++++++++++ itest/intercept_zero_conf_test.go | 3 +- itest/lspd_test.go | 19 +++------- lnd_interceptor.go | 16 +++----- 7 files changed, 111 insertions(+), 44 deletions(-) create mode 100644 itest/bob_offline_test.go diff --git a/cln_interceptor.go b/cln_interceptor.go index 71895ed..a459824 100644 --- a/cln_interceptor.go +++ b/cln_interceptor.go @@ -95,17 +95,15 @@ func (i *ClnHtlcInterceptor) OnHtlcAccepted(event *glightning.HtlcAcceptedEvent) paymentHashBytes, err := hex.DecodeString(event.Htlc.PaymentHash) if err != nil { log.Printf("hex.DecodeString(%v) error: %v", event.Htlc.PaymentHash, err) - return event.Fail(uint16(FAILURE_TEMPORARY_CHANNEL_FAILURE)), nil + return event.Fail(i.mapFailureCode(FAILURE_TEMPORARY_CHANNEL_FAILURE)), nil } interceptResult := intercept(paymentHashBytes, onion.ForwardAmount, uint32(event.Htlc.CltvExpiry)) switch interceptResult.action { case INTERCEPT_RESUME_WITH_ONION: return i.resumeWithOnion(event, interceptResult), nil - case INTERCEPT_FAIL_HTLC: - return event.Fail(uint16(FAILURE_TEMPORARY_CHANNEL_FAILURE)), nil case INTERCEPT_FAIL_HTLC_WITH_CODE: - return event.Fail(uint16(interceptResult.failureCode)), nil + return event.Fail(i.mapFailureCode(interceptResult.failureCode)), nil case INTERCEPT_RESUME: fallthrough default: @@ -118,7 +116,7 @@ func (i *ClnHtlcInterceptor) resumeWithOnion(event *glightning.HtlcAcceptedEvent newPayload, err := encodePayloadWithNextHop(event.Onion.Payload, interceptResult.channelId) if err != nil { log.Printf("encodePayloadWithNextHop error: %v", err) - return event.Fail(uint16(FAILURE_TEMPORARY_CHANNEL_FAILURE)) + return event.Fail(i.mapFailureCode(FAILURE_TEMPORARY_CHANNEL_FAILURE)) } chanId := lnwire.NewChanIDFromOutPoint(interceptResult.channelPoint) @@ -176,3 +174,17 @@ func encodePayloadWithNextHop(payloadHex string, channelId uint64) (string, erro } return hex.EncodeToString(newPayloadBuf.Bytes()), nil } + +func (i *ClnHtlcInterceptor) mapFailureCode(original interceptFailureCode) string { + switch original { + case FAILURE_TEMPORARY_CHANNEL_FAILURE: + return "1007" + case FAILURE_TEMPORARY_NODE_FAILURE: + return "2002" + case FAILURE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS: + return "4015" + default: + log.Printf("Unknown failure code %v, default to temporary channel failure.", original) + return "1007" // temporary channel failure + } +} diff --git a/go.mod b/go.mod index 77041de..d7e9acd 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.19 require ( github.com/aws/aws-sdk-go v1.30.20 - github.com/breez/lntest v0.0.11 + github.com/breez/lntest v0.0.12 github.com/btcsuite/btcd v0.23.3 github.com/btcsuite/btcd/btcec/v2 v2.2.1 github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 @@ -43,7 +43,6 @@ require ( ) require ( - github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344 // indirect github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect github.com/aead/siphash v1.0.1 // indirect github.com/andybalholm/brotli v1.0.3 // indirect @@ -137,7 +136,6 @@ require ( github.com/ulikunitz/xz v0.5.10 // indirect github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect - gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec // indirect go.etcd.io/bbolt v1.3.6 // indirect go.etcd.io/etcd/api/v3 v3.5.0 // indirect go.etcd.io/etcd/client/pkg/v3 v3.5.0 // indirect @@ -178,4 +176,4 @@ require ( replace github.com/lightningnetwork/lnd v0.15.4-beta => github.com/breez/lnd v0.15.0-beta.rc6.0.20220831104847-00b86a81e57a -replace github.com/niftynei/glightning v0.8.2 => github.com/breez/glightning v0.0.0-20221207132824-fb0b6f4f7483 +replace github.com/niftynei/glightning v0.8.2 => github.com/breez/glightning v0.0.0-20221216194006-8e9b65bdcbfc diff --git a/intercept.go b/intercept.go index ed04a45..c4c2f3e 100644 --- a/intercept.go +++ b/intercept.go @@ -22,14 +22,14 @@ type interceptAction int const ( INTERCEPT_RESUME interceptAction = 0 INTERCEPT_RESUME_WITH_ONION interceptAction = 1 - INTERCEPT_FAIL_HTLC interceptAction = 2 - INTERCEPT_FAIL_HTLC_WITH_CODE interceptAction = 3 + INTERCEPT_FAIL_HTLC_WITH_CODE interceptAction = 2 ) type interceptFailureCode uint16 var ( FAILURE_TEMPORARY_CHANNEL_FAILURE interceptFailureCode = 0x1007 + FAILURE_TEMPORARY_NODE_FAILURE interceptFailureCode = 0x2002 FAILURE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS interceptFailureCode = 0x4015 ) @@ -52,7 +52,8 @@ func intercept(reqPaymentHash []byte, reqOutgoingAmountMsat uint64, reqOutgoingE if err != nil { log.Printf("paymentInfo(%x) error: %v", reqPaymentHash, err) return interceptResult{ - action: INTERCEPT_FAIL_HTLC, + action: INTERCEPT_FAIL_HTLC_WITH_CODE, + failureCode: FAILURE_TEMPORARY_NODE_FAILURE, }, nil } log.Printf("paymentHash:%x\npaymentSecret:%x\ndestination:%x\nincomingAmountMsat:%v\noutgoingAmountMsat:%v", @@ -69,7 +70,8 @@ func intercept(reqPaymentHash []byte, reqOutgoingAmountMsat uint64, reqOutgoingE if err != nil { log.Printf("openChannel(%x, %v) err: %v", destination, incomingAmountMsat, err) return interceptResult{ - action: INTERCEPT_FAIL_HTLC, + action: INTERCEPT_FAIL_HTLC_WITH_CODE, + failureCode: FAILURE_TEMPORARY_CHANNEL_FAILURE, }, nil } } else { //probing @@ -90,7 +92,8 @@ func intercept(reqPaymentHash []byte, reqOutgoingAmountMsat uint64, reqOutgoingE if err != nil { log.Printf("btcec.ParsePubKey(%x): %v", destination, err) return interceptResult{ - action: INTERCEPT_FAIL_HTLC, + action: INTERCEPT_FAIL_HTLC_WITH_CODE, + failureCode: FAILURE_TEMPORARY_CHANNEL_FAILURE, }, nil } @@ -98,7 +101,8 @@ func intercept(reqPaymentHash []byte, reqOutgoingAmountMsat uint64, reqOutgoingE if err != nil { log.Printf("btcec.NewPrivateKey(): %v", err) return interceptResult{ - action: INTERCEPT_FAIL_HTLC, + action: INTERCEPT_FAIL_HTLC_WITH_CODE, + failureCode: FAILURE_TEMPORARY_CHANNEL_FAILURE, }, nil } @@ -119,7 +123,8 @@ func intercept(reqPaymentHash []byte, reqOutgoingAmountMsat uint64, reqOutgoingE if err != nil { log.Printf("hop.PackHopPayload(): %v", err) return interceptResult{ - action: INTERCEPT_FAIL_HTLC, + action: INTERCEPT_FAIL_HTLC_WITH_CODE, + failureCode: FAILURE_TEMPORARY_CHANNEL_FAILURE, }, nil } @@ -127,7 +132,8 @@ func intercept(reqPaymentHash []byte, reqOutgoingAmountMsat uint64, reqOutgoingE if err != nil { log.Printf("sphinx.NewHopPayload(): %v", err) return interceptResult{ - action: INTERCEPT_FAIL_HTLC, + action: INTERCEPT_FAIL_HTLC_WITH_CODE, + failureCode: FAILURE_TEMPORARY_CHANNEL_FAILURE, }, nil } @@ -143,7 +149,8 @@ func intercept(reqPaymentHash []byte, reqOutgoingAmountMsat uint64, reqOutgoingE if err != nil { log.Printf("sphinx.NewOnionPacket(): %v", err) return interceptResult{ - action: INTERCEPT_FAIL_HTLC, + action: INTERCEPT_FAIL_HTLC_WITH_CODE, + failureCode: FAILURE_TEMPORARY_CHANNEL_FAILURE, }, nil } var onionBlob bytes.Buffer @@ -151,7 +158,8 @@ func intercept(reqPaymentHash []byte, reqOutgoingAmountMsat uint64, reqOutgoingE if err != nil { log.Printf("sphinxPacket.Encode(): %v", err) return interceptResult{ - action: INTERCEPT_FAIL_HTLC, + action: INTERCEPT_FAIL_HTLC_WITH_CODE, + failureCode: FAILURE_TEMPORARY_CHANNEL_FAILURE, }, nil } diff --git a/itest/bob_offline_test.go b/itest/bob_offline_test.go new file mode 100644 index 0000000..2ad63ae --- /dev/null +++ b/itest/bob_offline_test.go @@ -0,0 +1,61 @@ +package itest + +import ( + "log" + "time" + + "github.com/breez/lntest" + lspd "github.com/breez/lspd/rpc" + "github.com/stretchr/testify/assert" +) + +func testFailureBobOffline(p *testParams) { + alice := lntest.NewClnNode(p.h, p.m, "Alice") + alice.Start() + alice.Fund(10000000) + p.lsp.LightningNode().Fund(10000000) + + log.Print("Opening channel between Alice and the lsp") + channel := alice.OpenChannel(p.lsp.LightningNode(), &lntest.OpenChannelOptions{ + AmountSat: publicChanAmount, + }) + channelId := alice.WaitForChannelReady(channel) + + log.Printf("Adding bob's invoices") + outerAmountMsat := uint64(2100000) + innerAmountMsat := calculateInnerAmountMsat(p.lsp, outerAmountMsat) + description := "Please pay me" + innerInvoice, outerInvoice := GenerateInvoices(p.BreezClient(), + generateInvoicesRequest{ + innerAmountMsat: innerAmountMsat, + outerAmountMsat: outerAmountMsat, + description: description, + lsp: p.lsp, + }) + + log.Print("Connecting bob to lspd") + p.BreezClient().Node().ConnectPeer(p.lsp.LightningNode()) + + // NOTE: We pretend to be paying fees to the lsp, but actually we won't. + log.Printf("Registering payment with lsp") + pretendAmount := outerAmountMsat - 2000000 + RegisterPayment(p.lsp, &lspd.PaymentInformation{ + PaymentHash: innerInvoice.paymentHash, + PaymentSecret: innerInvoice.paymentSecret, + Destination: p.BreezClient().Node().NodeId(), + IncomingAmountMsat: int64(outerAmountMsat), + OutgoingAmountMsat: int64(pretendAmount), + }) + + // Kill the mobile client + p.BreezClient().Stop() + + // TODO: Fix race waiting for htlc interceptor. + log.Printf("Waiting %v to allow htlc interceptor to activate.", htlcInterceptorDelay) + <-time.After(htlcInterceptorDelay) + + log.Printf("Alice paying") + route := constructRoute(p.lsp.LightningNode(), p.BreezClient().Node(), channelId, lntest.NewShortChanIDFromString("1x0x0"), outerAmountMsat) + _, err := alice.PayViaRoute(outerAmountMsat, outerInvoice.paymentHash, outerInvoice.paymentSecret, route) + assert.Contains(p.t, err.Error(), "WIRE_TEMPORARY_CHANNEL_FAILURE") +} diff --git a/itest/intercept_zero_conf_test.go b/itest/intercept_zero_conf_test.go index 9413b6d..32435f3 100644 --- a/itest/intercept_zero_conf_test.go +++ b/itest/intercept_zero_conf_test.go @@ -109,7 +109,8 @@ func testOpenZeroConfSingleHtlc(p *testParams) { <-time.After(htlcInterceptorDelay) log.Printf("Alice paying") route := constructRoute(p.lsp.LightningNode(), p.BreezClient().Node(), channelId, lntest.NewShortChanIDFromString("1x0x0"), outerAmountMsat) - payResp := alice.PayViaRoute(outerAmountMsat, outerInvoice.paymentHash, outerInvoice.paymentSecret, route) + payResp, err := alice.PayViaRoute(outerAmountMsat, outerInvoice.paymentHash, outerInvoice.paymentSecret, route) + lntest.CheckError(p.t, err) bobInvoice := p.BreezClient().Node().GetInvoice(payResp.PaymentHash) assert.Equal(p.t, payResp.PaymentPreimage, bobInvoice.PaymentPreimage) diff --git a/itest/lspd_test.go b/itest/lspd_test.go index 14b4ff5..69dfb93 100644 --- a/itest/lspd_test.go +++ b/itest/lspd_test.go @@ -3,7 +3,6 @@ package itest import ( "fmt" "log" - "sync" "testing" "time" @@ -51,18 +50,8 @@ func runTest(t *testing.T, testCase *testCase, prefix string, nodesFunc func(h * miner.Start() log.Printf("Creating lsp") lsp, c := nodesFunc(h, miner) - var wg sync.WaitGroup - wg.Add(2) - go func() { - lsp.Start() - wg.Done() - }() - - go func() { - c.Start() - wg.Done() - }() - wg.Wait() + lsp.Start() + c.Start() log.Printf("Run testcase") testCase.test(&testParams{ t: t, @@ -92,4 +81,8 @@ var allTestCases = []*testCase{ name: "testZeroReserve", test: testZeroReserve, }, + { + name: "testFailureBobOffline", + test: testFailureBobOffline, + }, } diff --git a/lnd_interceptor.go b/lnd_interceptor.go index 4c13c8b..85f2ee0 100644 --- a/lnd_interceptor.go +++ b/lnd_interceptor.go @@ -109,13 +109,11 @@ func (i *LndHtlcInterceptor) intercept() error { OutgoingRequestedChanId: uint64(interceptResult.channelId), OnionBlob: interceptResult.onionBlob, }) - case INTERCEPT_FAIL_HTLC: - failForwardSend(interceptorClient, request.IncomingCircuitKey) case INTERCEPT_FAIL_HTLC_WITH_CODE: interceptorClient.Send(&routerrpc.ForwardHtlcInterceptResponse{ IncomingCircuitKey: request.IncomingCircuitKey, Action: routerrpc.ResolveHoldForwardAction_FAIL, - FailureCode: mapFailureCode(interceptResult.failureCode), + FailureCode: i.mapFailureCode(interceptResult.failureCode), }) case INTERCEPT_RESUME: fallthrough @@ -138,20 +136,16 @@ func (i *LndHtlcInterceptor) intercept() error { } } -func mapFailureCode(original interceptFailureCode) lnrpc.Failure_FailureCode { +func (i *LndHtlcInterceptor) mapFailureCode(original interceptFailureCode) lnrpc.Failure_FailureCode { switch original { case FAILURE_TEMPORARY_CHANNEL_FAILURE: return lnrpc.Failure_TEMPORARY_CHANNEL_FAILURE + case FAILURE_TEMPORARY_NODE_FAILURE: + return lnrpc.Failure_TEMPORARY_NODE_FAILURE case FAILURE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS: return lnrpc.Failure_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS default: + log.Printf("Unknown failure code %v, default to temporary channel failure.", original) return lnrpc.Failure_TEMPORARY_CHANNEL_FAILURE } } - -func failForwardSend(interceptorClient routerrpc.Router_HtlcInterceptorClient, incomingCircuitKey *routerrpc.CircuitKey) { - interceptorClient.Send(&routerrpc.ForwardHtlcInterceptResponse{ - IncomingCircuitKey: incomingCircuitKey, - Action: routerrpc.ResolveHoldForwardAction_FAIL, - }) -} From 1730f04110ab90b0dbed9ddf850573fbf0f8db79 Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Fri, 16 Dec 2022 21:33:01 +0100 Subject: [PATCH 111/214] retry payment after Bob is online again --- go.mod | 2 +- itest/bob_offline_test.go | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index d7e9acd..2692692 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.19 require ( github.com/aws/aws-sdk-go v1.30.20 - github.com/breez/lntest v0.0.12 + github.com/breez/lntest v0.0.13 github.com/btcsuite/btcd v0.23.3 github.com/btcsuite/btcd/btcec/v2 v2.2.1 github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 diff --git a/itest/bob_offline_test.go b/itest/bob_offline_test.go index 2ad63ae..c325f71 100644 --- a/itest/bob_offline_test.go +++ b/itest/bob_offline_test.go @@ -48,6 +48,7 @@ func testFailureBobOffline(p *testParams) { }) // Kill the mobile client + log.Printf("Stopping breez client") p.BreezClient().Stop() // TODO: Fix race waiting for htlc interceptor. @@ -58,4 +59,12 @@ func testFailureBobOffline(p *testParams) { route := constructRoute(p.lsp.LightningNode(), p.BreezClient().Node(), channelId, lntest.NewShortChanIDFromString("1x0x0"), outerAmountMsat) _, err := alice.PayViaRoute(outerAmountMsat, outerInvoice.paymentHash, outerInvoice.paymentSecret, route) assert.Contains(p.t, err.Error(), "WIRE_TEMPORARY_CHANNEL_FAILURE") + + log.Printf("Starting breez client again") + p.BreezClient().Start() + p.BreezClient().Node().ConnectPeer(p.lsp.LightningNode()) + + log.Printf("Alice paying again") + _, err = alice.PayViaRoute(outerAmountMsat, outerInvoice.paymentHash, outerInvoice.paymentSecret, route) + assert.Nil(p.t, err) } From 3f2cf68a1320ca24aa92178ef72564a77a8e3f53 Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Mon, 19 Dec 2022 10:48:52 +0100 Subject: [PATCH 112/214] return 'pretend' amount from calc func --- itest/bob_offline_test.go | 6 ++---- itest/intercept_zero_conf_test.go | 12 ++++-------- itest/test_common.go | 21 +++++++++++++-------- itest/zero_reserve_test.go | 6 ++---- 4 files changed, 21 insertions(+), 24 deletions(-) diff --git a/itest/bob_offline_test.go b/itest/bob_offline_test.go index c325f71..bd7d505 100644 --- a/itest/bob_offline_test.go +++ b/itest/bob_offline_test.go @@ -23,7 +23,7 @@ func testFailureBobOffline(p *testParams) { log.Printf("Adding bob's invoices") outerAmountMsat := uint64(2100000) - innerAmountMsat := calculateInnerAmountMsat(p.lsp, outerAmountMsat) + innerAmountMsat, lspAmountMsat := calculateInnerAmountMsat(p.lsp, outerAmountMsat) description := "Please pay me" innerInvoice, outerInvoice := GenerateInvoices(p.BreezClient(), generateInvoicesRequest{ @@ -36,15 +36,13 @@ func testFailureBobOffline(p *testParams) { log.Print("Connecting bob to lspd") p.BreezClient().Node().ConnectPeer(p.lsp.LightningNode()) - // NOTE: We pretend to be paying fees to the lsp, but actually we won't. log.Printf("Registering payment with lsp") - pretendAmount := outerAmountMsat - 2000000 RegisterPayment(p.lsp, &lspd.PaymentInformation{ PaymentHash: innerInvoice.paymentHash, PaymentSecret: innerInvoice.paymentSecret, Destination: p.BreezClient().Node().NodeId(), IncomingAmountMsat: int64(outerAmountMsat), - OutgoingAmountMsat: int64(pretendAmount), + OutgoingAmountMsat: int64(lspAmountMsat), }) // Kill the mobile client diff --git a/itest/intercept_zero_conf_test.go b/itest/intercept_zero_conf_test.go index 32435f3..45ba809 100644 --- a/itest/intercept_zero_conf_test.go +++ b/itest/intercept_zero_conf_test.go @@ -25,7 +25,7 @@ func testOpenZeroConfChannelOnReceive(p *testParams) { log.Printf("Adding bob's invoices") outerAmountMsat := uint64(2100000) - innerAmountMsat := calculateInnerAmountMsat(p.lsp, outerAmountMsat) + innerAmountMsat, lspAmountMsat := calculateInnerAmountMsat(p.lsp, outerAmountMsat) description := "Please pay me" innerInvoice, outerInvoice := GenerateInvoices(p.BreezClient(), generateInvoicesRequest{ @@ -38,15 +38,13 @@ func testOpenZeroConfChannelOnReceive(p *testParams) { log.Print("Connecting bob to lspd") p.BreezClient().Node().ConnectPeer(p.lsp.LightningNode()) - // NOTE: We pretend to be paying fees to the lsp, but actually we won't. log.Printf("Registering payment with lsp") - pretendAmount := outerAmountMsat - 2000000 RegisterPayment(p.lsp, &lspd.PaymentInformation{ PaymentHash: innerInvoice.paymentHash, PaymentSecret: innerInvoice.paymentSecret, Destination: p.BreezClient().Node().NodeId(), IncomingAmountMsat: int64(outerAmountMsat), - OutgoingAmountMsat: int64(pretendAmount), + OutgoingAmountMsat: int64(lspAmountMsat), }) // TODO: Fix race waiting for htlc interceptor. @@ -80,7 +78,7 @@ func testOpenZeroConfSingleHtlc(p *testParams) { log.Printf("Adding bob's invoices") outerAmountMsat := uint64(2100000) - innerAmountMsat := calculateInnerAmountMsat(p.lsp, outerAmountMsat) + innerAmountMsat, lspAmountMsat := calculateInnerAmountMsat(p.lsp, outerAmountMsat) description := "Please pay me" innerInvoice, outerInvoice := GenerateInvoices(p.BreezClient(), generateInvoicesRequest{ @@ -93,15 +91,13 @@ func testOpenZeroConfSingleHtlc(p *testParams) { log.Print("Connecting bob to lspd") p.BreezClient().Node().ConnectPeer(p.lsp.LightningNode()) - // NOTE: We pretend to be paying fees to the lsp, but actually we won't. log.Printf("Registering payment with lsp") - pretendAmount := outerAmountMsat - 2000000 RegisterPayment(p.lsp, &lspd.PaymentInformation{ PaymentHash: innerInvoice.paymentHash, PaymentSecret: innerInvoice.paymentSecret, Destination: p.BreezClient().Node().NodeId(), IncomingAmountMsat: int64(outerAmountMsat), - OutgoingAmountMsat: int64(pretendAmount), + OutgoingAmountMsat: int64(lspAmountMsat), }) // TODO: Fix race waiting for htlc interceptor. diff --git a/itest/test_common.go b/itest/test_common.go index c83b8fe..5f388df 100644 --- a/itest/test_common.go +++ b/itest/test_common.go @@ -26,16 +26,21 @@ func AssertChannelCapacity( assert.Equal(t, ((outerAmountMsat/1000)+100000)*1000, capacityMsat) } -func calculateInnerAmountMsat(lsp LspNode, outerAmountMsat uint64) uint64 { - if lsp.SupportsChargingFees() { - fee := outerAmountMsat * 40 / 10_000 / 1_000 * 1_000 - if fee < 2000000 { - fee = 2000000 - } +func calculateInnerAmountMsat(lsp LspNode, outerAmountMsat uint64) (uint64, uint64) { + fee := outerAmountMsat * 40 / 10_000 / 1_000 * 1_000 + if fee < 2000000 { + fee = 2000000 + } - return outerAmountMsat - fee + inner := outerAmountMsat - fee + + // NOTE: If the LSP does not support charging fees (the CLN version doesn't) + // We have to pretend in the registerpayment call that the LSP WILL charge + // fees. If we update the CLN lsp to charge fees, this check can be removed. + if lsp.SupportsChargingFees() { + return inner, inner } else { - return outerAmountMsat + return outerAmountMsat, inner } } diff --git a/itest/zero_reserve_test.go b/itest/zero_reserve_test.go index f9f7486..8dd4fdf 100644 --- a/itest/zero_reserve_test.go +++ b/itest/zero_reserve_test.go @@ -23,7 +23,7 @@ func testZeroReserve(p *testParams) { log.Printf("Adding bob's invoices") outerAmountMsat := uint64(2100000) - innerAmountMsat := calculateInnerAmountMsat(p.lsp, outerAmountMsat) + innerAmountMsat, lspAmountMsat := calculateInnerAmountMsat(p.lsp, outerAmountMsat) description := "Please pay me" innerInvoice, outerInvoice := GenerateInvoices(p.BreezClient(), generateInvoicesRequest{ @@ -36,15 +36,13 @@ func testZeroReserve(p *testParams) { log.Print("Connecting bob to lspd") p.BreezClient().Node().ConnectPeer(p.lsp.LightningNode()) - // NOTE: We pretend to be paying fees to the lsp, but actually we won't. log.Printf("Registering payment with lsp") - pretendAmount := outerAmountMsat - 2000000 RegisterPayment(p.lsp, &lspd.PaymentInformation{ PaymentHash: innerInvoice.paymentHash, PaymentSecret: innerInvoice.paymentSecret, Destination: p.BreezClient().Node().NodeId(), IncomingAmountMsat: int64(outerAmountMsat), - OutgoingAmountMsat: int64(pretendAmount), + OutgoingAmountMsat: int64(lspAmountMsat), }) // TODO: Fix race waiting for htlc interceptor. From 434d2431d24dc3745a280e3fbe9123cd0a3cd912 Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Mon, 19 Dec 2022 11:39:12 +0100 Subject: [PATCH 113/214] fix amount_msat is a number --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 2692692..33a2416 100644 --- a/go.mod +++ b/go.mod @@ -176,4 +176,4 @@ require ( replace github.com/lightningnetwork/lnd v0.15.4-beta => github.com/breez/lnd v0.15.0-beta.rc6.0.20220831104847-00b86a81e57a -replace github.com/niftynei/glightning v0.8.2 => github.com/breez/glightning v0.0.0-20221216194006-8e9b65bdcbfc +replace github.com/niftynei/glightning v0.8.2 => github.com/breez/glightning v0.0.0-20221219103549-0e2a13b9b3ed From 615bdb8bb5cbef91c2d25feca0586a817716ba9a Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Mon, 19 Dec 2022 13:26:36 +0100 Subject: [PATCH 114/214] test no onchain balance temp chan failure --- itest/lspd_test.go | 4 +++ itest/no_balance_test.go | 55 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+) create mode 100644 itest/no_balance_test.go diff --git a/itest/lspd_test.go b/itest/lspd_test.go index 69dfb93..b7c2ca3 100644 --- a/itest/lspd_test.go +++ b/itest/lspd_test.go @@ -85,4 +85,8 @@ var allTestCases = []*testCase{ name: "testFailureBobOffline", test: testFailureBobOffline, }, + { + name: "testNoBalance", + test: testNoBalance, + }, } diff --git a/itest/no_balance_test.go b/itest/no_balance_test.go new file mode 100644 index 0000000..251f753 --- /dev/null +++ b/itest/no_balance_test.go @@ -0,0 +1,55 @@ +package itest + +import ( + "log" + "time" + + "github.com/breez/lntest" + lspd "github.com/breez/lspd/rpc" + "github.com/stretchr/testify/assert" +) + +func testNoBalance(p *testParams) { + alice := lntest.NewClnNode(p.h, p.m, "Alice") + alice.Start() + alice.Fund(10000000) + + log.Print("Opening channel between Alice and the lsp") + channel := alice.OpenChannel(p.lsp.LightningNode(), &lntest.OpenChannelOptions{ + AmountSat: publicChanAmount, + }) + channelId := alice.WaitForChannelReady(channel) + + log.Printf("Adding bob's invoices") + outerAmountMsat := uint64(2100000) + innerAmountMsat, lspAmountMsat := calculateInnerAmountMsat(p.lsp, outerAmountMsat) + description := "Please pay me" + innerInvoice, outerInvoice := GenerateInvoices(p.BreezClient(), + generateInvoicesRequest{ + innerAmountMsat: innerAmountMsat, + outerAmountMsat: outerAmountMsat, + description: description, + lsp: p.lsp, + }) + + log.Print("Connecting bob to lspd") + p.BreezClient().Node().ConnectPeer(p.lsp.LightningNode()) + + log.Printf("Registering payment with lsp") + RegisterPayment(p.lsp, &lspd.PaymentInformation{ + PaymentHash: innerInvoice.paymentHash, + PaymentSecret: innerInvoice.paymentSecret, + Destination: p.BreezClient().Node().NodeId(), + IncomingAmountMsat: int64(outerAmountMsat), + OutgoingAmountMsat: int64(lspAmountMsat), + }) + + // TODO: Fix race waiting for htlc interceptor. + log.Printf("Waiting %v to allow htlc interceptor to activate.", htlcInterceptorDelay) + <-time.After(htlcInterceptorDelay) + + log.Printf("Alice paying") + route := constructRoute(p.lsp.LightningNode(), p.BreezClient().Node(), channelId, lntest.NewShortChanIDFromString("1x0x0"), outerAmountMsat) + _, err := alice.PayViaRoute(outerAmountMsat, outerInvoice.paymentHash, outerInvoice.paymentSecret, route) + assert.Contains(p.t, err.Error(), "WIRE_TEMPORARY_CHANNEL_FAILURE") +} From fecb4d7fbc8ccd8efa7e695ae96b3fe63c0dde0c Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Mon, 19 Dec 2022 15:43:50 +0100 Subject: [PATCH 115/214] add a test for regular forwards --- go.mod | 2 +- itest/lspd_test.go | 4 +++ itest/regular_forward_test.go | 53 +++++++++++++++++++++++++++++++++++ 3 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 itest/regular_forward_test.go diff --git a/go.mod b/go.mod index 33a2416..bb38b44 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.19 require ( github.com/aws/aws-sdk-go v1.30.20 - github.com/breez/lntest v0.0.13 + github.com/breez/lntest v0.0.15 github.com/btcsuite/btcd v0.23.3 github.com/btcsuite/btcd/btcec/v2 v2.2.1 github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 diff --git a/itest/lspd_test.go b/itest/lspd_test.go index b7c2ca3..6360539 100644 --- a/itest/lspd_test.go +++ b/itest/lspd_test.go @@ -89,4 +89,8 @@ var allTestCases = []*testCase{ name: "testNoBalance", test: testNoBalance, }, + { + name: "testRegularForward", + test: testRegularForward, + }, } diff --git a/itest/regular_forward_test.go b/itest/regular_forward_test.go new file mode 100644 index 0000000..61c22a7 --- /dev/null +++ b/itest/regular_forward_test.go @@ -0,0 +1,53 @@ +package itest + +import ( + "log" + "time" + + "github.com/breez/lntest" + "github.com/stretchr/testify/assert" +) + +func testRegularForward(p *testParams) { + alice := lntest.NewClnNode(p.h, p.m, "Alice") + alice.Start() + alice.Fund(10000000) + p.lsp.LightningNode().Fund(10000000) + p.BreezClient().Node().Fund(100000) + + log.Print("Opening channel between Alice and the lsp") + channelAL := alice.OpenChannel(p.lsp.LightningNode(), &lntest.OpenChannelOptions{ + AmountSat: publicChanAmount, + IsPublic: true, + }) + + log.Print("Opening channel between lsp and Breez client") + channelLB := p.lsp.LightningNode().OpenChannel(p.BreezClient().Node(), &lntest.OpenChannelOptions{ + AmountSat: 200000, + IsPublic: false, + }) + + log.Print("Waiting for channel between Alice and the lsp to be ready.") + alice.WaitForChannelReady(channelAL) + log.Print("Waiting for channel between LSP and Bob to be ready.") + p.lsp.LightningNode().WaitForChannelReady(channelLB) + p.BreezClient().Node().WaitForChannelReady(channelLB) + + // TODO: Fix race waiting for htlc interceptor. + log.Printf("Waiting %v to allow htlc interceptor to activate.", htlcInterceptorDelay) + <-time.After(htlcInterceptorDelay) + + log.Printf("Adding bob's invoice") + amountMsat := uint64(2100000) + bobInvoice := p.BreezClient().Node().CreateBolt11Invoice(&lntest.CreateInvoiceOptions{ + AmountMsat: amountMsat, + }) + log.Printf(bobInvoice.Bolt11) + + log.Printf("Alice paying") + payResp := alice.Pay(bobInvoice.Bolt11) + invoiceResult := p.BreezClient().Node().GetInvoice(bobInvoice.PaymentHash) + + assert.Equal(p.t, payResp.PaymentPreimage, invoiceResult.PaymentPreimage) + assert.Equal(p.t, amountMsat, invoiceResult.AmountReceivedMsat) +} From 913c9f9ae9d7ff7f7b5ba6f245f5041e41958e17 Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Mon, 19 Dec 2022 13:20:54 +0100 Subject: [PATCH 116/214] adds a test for probing with probing-01: --- itest/lspd_test.go | 4 +++ itest/probing_test.go | 74 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+) create mode 100644 itest/probing_test.go diff --git a/itest/lspd_test.go b/itest/lspd_test.go index 6360539..4c69ad4 100644 --- a/itest/lspd_test.go +++ b/itest/lspd_test.go @@ -93,4 +93,8 @@ var allTestCases = []*testCase{ name: "testRegularForward", test: testRegularForward, }, + { + name: "testProbing", + test: testProbing, + }, } diff --git a/itest/probing_test.go b/itest/probing_test.go new file mode 100644 index 0000000..a11818e --- /dev/null +++ b/itest/probing_test.go @@ -0,0 +1,74 @@ +package itest + +import ( + "crypto/sha256" + "log" + "time" + + "github.com/breez/lntest" + lspd "github.com/breez/lspd/rpc" + "github.com/stretchr/testify/assert" +) + +func testProbing(p *testParams) { + alice := lntest.NewClnNode(p.h, p.m, "Alice") + alice.Start() + alice.Fund(10000000) + p.lsp.LightningNode().Fund(10000000) + + log.Print("Opening channel between Alice and the lsp") + channel := alice.OpenChannel(p.lsp.LightningNode(), &lntest.OpenChannelOptions{ + AmountSat: publicChanAmount, + }) + channelId := alice.WaitForChannelReady(channel) + + log.Printf("Adding bob's invoices") + outerAmountMsat := uint64(2100000) + innerAmountMsat, lspAmountMsat := calculateInnerAmountMsat(p.lsp, outerAmountMsat) + description := "Please pay me" + innerInvoice, outerInvoice := GenerateInvoices(p.BreezClient(), + generateInvoicesRequest{ + innerAmountMsat: innerAmountMsat, + outerAmountMsat: outerAmountMsat, + description: description, + lsp: p.lsp, + }) + + log.Print("Connecting bob to lspd") + p.BreezClient().Node().ConnectPeer(p.lsp.LightningNode()) + + log.Printf("Registering payment with lsp") + RegisterPayment(p.lsp, &lspd.PaymentInformation{ + PaymentHash: innerInvoice.paymentHash, + PaymentSecret: innerInvoice.paymentSecret, + Destination: p.BreezClient().Node().NodeId(), + IncomingAmountMsat: int64(outerAmountMsat), + OutgoingAmountMsat: int64(lspAmountMsat), + }) + + // TODO: Fix race waiting for htlc interceptor. + log.Printf("Waiting %v to allow htlc interceptor to activate.", htlcInterceptorDelay) + <-time.After(htlcInterceptorDelay) + + h := sha256.New() + _, _ = h.Write([]byte("probing-01:")) + _, _ = h.Write(outerInvoice.paymentHash) + fakePaymentHash := h.Sum(nil) + + log.Printf("Alice paying with fake payment hash with Bob online %x", fakePaymentHash) + route := constructRoute(p.lsp.LightningNode(), p.BreezClient().Node(), channelId, lntest.NewShortChanIDFromString("1x0x0"), outerAmountMsat) + _, err := alice.PayViaRoute(outerAmountMsat, fakePaymentHash, outerInvoice.paymentSecret, route) + + // Expect temporary channel failure if the peer is online + assert.Contains(p.t, err.Error(), "WIRE_TEMPORARY_CHANNEL_FAILURE") + + // Kill the mobile client + log.Printf("Stopping breez client") + p.BreezClient().Stop() + + log.Printf("Alice paying with fake payment hash with Bob offline %x", fakePaymentHash) + _, err = alice.PayViaRoute(outerAmountMsat, fakePaymentHash, outerInvoice.paymentSecret, route) + + // Expect incorrect or unknown payment details if the peer is offline + assert.Contains(p.t, err.Error(), "WIRE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS") +} From 9471d86a20ec1d6d7e46c97b37634d7b7593dcc9 Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Thu, 22 Dec 2022 13:01:04 +0100 Subject: [PATCH 117/214] fix incorrect payment details hex code --- cln_interceptor.go | 2 +- intercept.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cln_interceptor.go b/cln_interceptor.go index a459824..02f6903 100644 --- a/cln_interceptor.go +++ b/cln_interceptor.go @@ -182,7 +182,7 @@ func (i *ClnHtlcInterceptor) mapFailureCode(original interceptFailureCode) strin case FAILURE_TEMPORARY_NODE_FAILURE: return "2002" case FAILURE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS: - return "4015" + return "400F" default: log.Printf("Unknown failure code %v, default to temporary channel failure.", original) return "1007" // temporary channel failure diff --git a/intercept.go b/intercept.go index c4c2f3e..92edc0b 100644 --- a/intercept.go +++ b/intercept.go @@ -30,7 +30,7 @@ type interceptFailureCode uint16 var ( FAILURE_TEMPORARY_CHANNEL_FAILURE interceptFailureCode = 0x1007 FAILURE_TEMPORARY_NODE_FAILURE interceptFailureCode = 0x2002 - FAILURE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS interceptFailureCode = 0x4015 + FAILURE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS interceptFailureCode = 0x400F ) var payHashGroup singleflight.Group From c939a2d493e5397d8d64e8b240824019c4cfca60 Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Thu, 22 Dec 2022 13:11:47 +0100 Subject: [PATCH 118/214] point to the correct commit in readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3a1725f..03aed8c 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ In order to run the integration tests, you need: - Docker running - python3 installed - A development build of lightningd v22.11 -- lnd v0.15.4 lsp version https://github.com/breez/lnd/tree/breez-node-v0.15.4 +- lnd v0.15.4 lsp version https://github.com/breez/lnd/commit/6ee6b89e3a4e3f2776643a75a9100d13a2090725 - lnd v0.15.3 breez client version https://github.com/breez/lnd/commit/e1570b327b5de52d03817ad516d0bdfa71797c64 - bitcoind (tested with v23.0) - bitcoin-cli (tested with v23.0) From 58367bafef099e1834abf0b08f4ab759407588d2 Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Thu, 22 Dec 2022 13:54:46 +0100 Subject: [PATCH 119/214] return incorrect details on probing when connected --- intercept.go | 2 +- itest/probing_test.go | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/intercept.go b/intercept.go index 92edc0b..7d522d8 100644 --- a/intercept.go +++ b/intercept.go @@ -77,7 +77,7 @@ func intercept(reqPaymentHash []byte, reqOutgoingAmountMsat uint64, reqOutgoingE } else { //probing failureCode := FAILURE_TEMPORARY_CHANNEL_FAILURE isConnected, _ := client.IsConnected(destination) - if err != nil || !isConnected { + if isConnected { failureCode = FAILURE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS } diff --git a/itest/probing_test.go b/itest/probing_test.go index a11818e..fb53fc1 100644 --- a/itest/probing_test.go +++ b/itest/probing_test.go @@ -59,8 +59,8 @@ func testProbing(p *testParams) { route := constructRoute(p.lsp.LightningNode(), p.BreezClient().Node(), channelId, lntest.NewShortChanIDFromString("1x0x0"), outerAmountMsat) _, err := alice.PayViaRoute(outerAmountMsat, fakePaymentHash, outerInvoice.paymentSecret, route) - // Expect temporary channel failure if the peer is online - assert.Contains(p.t, err.Error(), "WIRE_TEMPORARY_CHANNEL_FAILURE") + // Expect incorrect or unknown payment details if the peer is online + assert.Contains(p.t, err.Error(), "WIRE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS") // Kill the mobile client log.Printf("Stopping breez client") @@ -69,6 +69,6 @@ func testProbing(p *testParams) { log.Printf("Alice paying with fake payment hash with Bob offline %x", fakePaymentHash) _, err = alice.PayViaRoute(outerAmountMsat, fakePaymentHash, outerInvoice.paymentSecret, route) - // Expect incorrect or unknown payment details if the peer is offline - assert.Contains(p.t, err.Error(), "WIRE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS") + // Expect temporary channel failure if the peer is offline + assert.Contains(p.t, err.Error(), "WIRE_TEMPORARY_CHANNEL_FAILURE") } From c42db2f96882e83ef3850f4a5ffe4cf1e009e8aa Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Thu, 22 Dec 2022 15:31:48 +0100 Subject: [PATCH 120/214] fix hop hints for lnd regular payment test --- go.mod | 2 +- itest/regular_forward_test.go | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index bb38b44..d667517 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.19 require ( github.com/aws/aws-sdk-go v1.30.20 - github.com/breez/lntest v0.0.15 + github.com/breez/lntest v0.0.16 github.com/btcsuite/btcd v0.23.3 github.com/btcsuite/btcd/btcec/v2 v2.2.1 github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 diff --git a/itest/regular_forward_test.go b/itest/regular_forward_test.go index 61c22a7..cd788b6 100644 --- a/itest/regular_forward_test.go +++ b/itest/regular_forward_test.go @@ -40,7 +40,8 @@ func testRegularForward(p *testParams) { log.Printf("Adding bob's invoice") amountMsat := uint64(2100000) bobInvoice := p.BreezClient().Node().CreateBolt11Invoice(&lntest.CreateInvoiceOptions{ - AmountMsat: amountMsat, + AmountMsat: amountMsat, + IncludeHopHints: true, }) log.Printf(bobInvoice.Bolt11) From b6894e7337a81c7de6447a54241814fede66fef6 Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Thu, 22 Dec 2022 15:33:09 +0100 Subject: [PATCH 121/214] always return incorrect details on probes --- intercept.go | 8 +------- itest/probing_test.go | 2 +- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/intercept.go b/intercept.go index 7d522d8..81ffea2 100644 --- a/intercept.go +++ b/intercept.go @@ -75,15 +75,9 @@ func intercept(reqPaymentHash []byte, reqOutgoingAmountMsat uint64, reqOutgoingE }, nil } } else { //probing - failureCode := FAILURE_TEMPORARY_CHANNEL_FAILURE - isConnected, _ := client.IsConnected(destination) - if isConnected { - failureCode = FAILURE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS - } - return interceptResult{ action: INTERCEPT_FAIL_HTLC_WITH_CODE, - failureCode: failureCode, + failureCode: FAILURE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS, }, nil } } diff --git a/itest/probing_test.go b/itest/probing_test.go index fb53fc1..2fddbcd 100644 --- a/itest/probing_test.go +++ b/itest/probing_test.go @@ -70,5 +70,5 @@ func testProbing(p *testParams) { _, err = alice.PayViaRoute(outerAmountMsat, fakePaymentHash, outerInvoice.paymentSecret, route) // Expect temporary channel failure if the peer is offline - assert.Contains(p.t, err.Error(), "WIRE_TEMPORARY_CHANNEL_FAILURE") + assert.Contains(p.t, err.Error(), "WIRE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS") } From a7631dcddf6fe3098e330f701e12e37f8e96fe36 Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Tue, 3 Jan 2023 19:54:04 +0100 Subject: [PATCH 122/214] make prepared statement understand bigint --- db.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/db.go b/db.go index 4b23902..b68cbf5 100644 --- a/db.go +++ b/db.go @@ -86,8 +86,8 @@ func insertChannel(initialChanID, confirmedChanId uint64, channelPoint string, n query := `INSERT INTO channels (initial_chanid, confirmed_chanid, channel_point, nodeid, last_update) - VALUES ($1, NULLIF($2, 0), $3, $4, $5) - ON CONFLICT (channel_point) DO UPDATE SET confirmed_chanid=NULLIF($2,0), last_update=$5` + VALUES ($1, NULLIF($2, 0::int8), $3, $4, $5) + ON CONFLICT (channel_point) DO UPDATE SET confirmed_chanid=NULLIF($2, 0::int8), last_update=$5` c, err := pgxPool.Exec(context.Background(), query, int64(initialChanID), int64(confirmedChanId), channelPoint, nodeID, lastUpdate) From 5b67c399fd5e61b5fa87e96a2c2465c76e2443b0 Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Fri, 23 Dec 2022 18:19:05 +0100 Subject: [PATCH 123/214] move short channel id to basetypes dir --- short_channel_id.go => basetypes/short_channel_id.go | 2 +- cln_client.go | 7 ++++--- lightning_client.go | 9 ++++++--- lnd_client.go | 5 +++-- 4 files changed, 14 insertions(+), 9 deletions(-) rename short_channel_id.go => basetypes/short_channel_id.go (98%) diff --git a/short_channel_id.go b/basetypes/short_channel_id.go similarity index 98% rename from short_channel_id.go rename to basetypes/short_channel_id.go index 8ce0b94..4b88c7a 100644 --- a/short_channel_id.go +++ b/basetypes/short_channel_id.go @@ -1,4 +1,4 @@ -package main +package basetypes import ( "fmt" diff --git a/cln_client.go b/cln_client.go index 56ac320..cc9f088 100644 --- a/cln_client.go +++ b/cln_client.go @@ -5,6 +5,7 @@ import ( "fmt" "log" + "github.com/breez/lspd/basetypes" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" "github.com/niftynei/glightning/glightning" @@ -121,12 +122,12 @@ func (c *ClnClient) GetChannel(peerID []byte, channelPoint wire.OutPoint) (*GetC for _, c := range peer.Channels { log.Printf("getChannel destination: %s, Short channel id: %v, local alias: %v , FundingTxID:%v, State:%v ", pubkey, c.ShortChannelId, c.Alias.Local, c.FundingTxId, c.State) if slices.Contains(OPEN_STATUSES, c.State) && c.FundingTxId == fundingTxID { - confirmedChanID, err := NewShortChannelIDFromString(c.ShortChannelId) + confirmedChanID, err := basetypes.NewShortChannelIDFromString(c.ShortChannelId) if err != nil { fmt.Printf("NewShortChannelIDFromString %v error: %v", c.ShortChannelId, err) return nil, err } - initialChanID, err := NewShortChannelIDFromString(c.Alias.Local) + initialChanID, err := basetypes.NewShortChannelIDFromString(c.Alias.Local) if err != nil { fmt.Printf("NewShortChannelIDFromString %v error: %v", c.Alias.Local, err) return nil, err @@ -176,7 +177,7 @@ func (c *ClnClient) GetClosedChannels(nodeID string, channelPoints map[string]ui lookup := make(map[string]uint64) for _, c := range peer.Channels { if slices.Contains(CLOSING_STATUSES, c.State) { - cid, err := NewShortChannelIDFromString(c.ShortChannelId) + cid, err := basetypes.NewShortChannelIDFromString(c.ShortChannelId) if err != nil { log.Printf("CLN: GetClosedChannels NewShortChannelIDFromString(%v) error: %v", c.ShortChannelId, err) continue diff --git a/lightning_client.go b/lightning_client.go index a89a38d..7f10124 100644 --- a/lightning_client.go +++ b/lightning_client.go @@ -1,6 +1,9 @@ package main -import "github.com/btcsuite/btcd/wire" +import ( + "github.com/breez/lspd/basetypes" + "github.com/btcsuite/btcd/wire" +) type GetInfoResult struct { Alias string @@ -8,8 +11,8 @@ type GetInfoResult struct { } type GetChannelResult struct { - InitialChannelID ShortChannelID - ConfirmedChannelID ShortChannelID + InitialChannelID basetypes.ShortChannelID + ConfirmedChannelID basetypes.ShortChannelID } type OpenChannelRequest struct { diff --git a/lnd_client.go b/lnd_client.go index f0582d7..f98054c 100644 --- a/lnd_client.go +++ b/lnd_client.go @@ -9,6 +9,7 @@ import ( "os" "strings" + "github.com/breez/lspd/basetypes" "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/htlcswitch/hop" "github.com/lightningnetwork/lnd/lnrpc" @@ -140,8 +141,8 @@ func (c *LndClient) GetChannel(peerID []byte, channelPoint wire.OutPoint) (*GetC } } return &GetChannelResult{ - InitialChannelID: ShortChannelID(c.ChanId), - ConfirmedChannelID: ShortChannelID(confirmedChanId), + InitialChannelID: basetypes.ShortChannelID(c.ChanId), + ConfirmedChannelID: basetypes.ShortChannelID(confirmedChanId), }, nil } } From 36c7f5f616a7b584f95c86176c05442e51d67d61 Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Fri, 23 Dec 2022 18:19:55 +0100 Subject: [PATCH 124/214] implement cln plugin wrapper with grpc streams --- cln_plugin/cln_plugin.go | 117 ++++++ cln_plugin/cln_plugin.pb.go | 674 ++++++++++++++++++++++++++++++ cln_plugin/cln_plugin_grpc.pb.go | 137 ++++++ cln_plugin/cmd/main.go | 31 ++ cln_plugin/genproto.sh | 5 + cln_plugin/proto/cln_plugin.proto | 46 ++ cln_plugin/sample.env | 1 + cln_plugin/server.go | 225 ++++++++++ 8 files changed, 1236 insertions(+) create mode 100644 cln_plugin/cln_plugin.go create mode 100644 cln_plugin/cln_plugin.pb.go create mode 100644 cln_plugin/cln_plugin_grpc.pb.go create mode 100644 cln_plugin/cmd/main.go create mode 100755 cln_plugin/genproto.sh create mode 100644 cln_plugin/proto/cln_plugin.proto create mode 100644 cln_plugin/sample.env create mode 100644 cln_plugin/server.go diff --git a/cln_plugin/cln_plugin.go b/cln_plugin/cln_plugin.go new file mode 100644 index 0000000..3a8442f --- /dev/null +++ b/cln_plugin/cln_plugin.go @@ -0,0 +1,117 @@ +package cln_plugin + +import ( + "encoding/hex" + "log" + "os" + + "github.com/breez/lspd/basetypes" + "github.com/niftynei/glightning/glightning" +) + +type ClnPlugin struct { + server *server + plugin *glightning.Plugin +} + +func NewClnPlugin(server *server) *ClnPlugin { + c := &ClnPlugin{ + server: server, + } + + c.plugin = glightning.NewPlugin(c.onInit) + c.plugin.RegisterHooks(&glightning.Hooks{ + HtlcAccepted: c.onHtlcAccepted, + }) + + return c +} + +func (c *ClnPlugin) Start() error { + err := c.plugin.Start(os.Stdin, os.Stdout) + if err != nil { + log.Printf("Plugin error: %v", err) + return err + } + + return nil +} + +func (c *ClnPlugin) Stop() { + c.plugin.Stop() + c.server.Stop() +} + +func (c *ClnPlugin) onInit(plugin *glightning.Plugin, options map[string]glightning.Option, config *glightning.Config) { + log.Printf("successfully init'd! %v\n", config.RpcFile) + + log.Printf("Starting htlc grpc server.") + go func() { + err := c.server.Start() + if err == nil { + log.Printf("WARNING server stopped.") + } else { + log.Printf("ERROR Server stopped with error: %v", err) + } + }() + + //lightning server + clientcln := glightning.NewLightning() + clientcln.SetTimeout(60) + clientcln.StartUp(config.RpcFile, config.LightningDir) + + log.Printf("successfull clientcln.StartUp") +} + +func (c *ClnPlugin) onHtlcAccepted(event *glightning.HtlcAcceptedEvent) (*glightning.HtlcAcceptedResponse, error) { + payload, err := hex.DecodeString(event.Onion.Payload) + if err != nil { + log.Printf("ERROR failed to decode payload %s: %v", event.Onion.Payload, err) + return nil, err + } + scid, err := basetypes.NewShortChannelIDFromString(event.Onion.ShortChannelId) + if err != nil { + log.Printf("ERROR failed to decode short channel id %s: %v", event.Onion.ShortChannelId, err) + return nil, err + } + ph, err := hex.DecodeString(event.Htlc.PaymentHash) + if err != nil { + log.Printf("ERROR failed to decode payment hash %s: %v", event.Onion.ShortChannelId, err) + return nil, err + } + + resp := c.server.Send(&HtlcAccepted{ + Onion: &Onion{ + Payload: payload, + ShortChannelId: uint64(*scid), + ForwardAmountMsat: event.Onion.ForwardAmount, + }, + Htlc: &HtlcOffer{ + AmountMsat: event.Htlc.AmountMilliSatoshi, + CltvExpiryRelative: uint32(event.Htlc.CltvExpiryRelative), + CltvExpiry: uint32(event.Htlc.CltvExpiry), + PaymentHash: ph, + }, + }) + + _, ok := resp.Outcome.(*HtlcResolution_Continue) + if ok { + return event.Continue(), nil + } + + cont, ok := resp.Outcome.(*HtlcResolution_ContinueWith) + if ok { + chanId := hex.EncodeToString(cont.ContinueWith.ChannelId) + pl := hex.EncodeToString(cont.ContinueWith.Payload) + return event.ContinueWith(chanId, pl), nil + } + + fail, ok := resp.Outcome.(*HtlcResolution_Fail) + if ok { + fm := hex.EncodeToString(fail.Fail.FailureMessage) + return event.Fail(fm), err + } + + log.Printf("Unexpected htlc resolution type %T: %+v", resp.Outcome, resp.Outcome) + return event.Fail("1007"), nil // temporary channel failure +} diff --git a/cln_plugin/cln_plugin.pb.go b/cln_plugin/cln_plugin.pb.go new file mode 100644 index 0000000..181a0f0 --- /dev/null +++ b/cln_plugin/cln_plugin.pb.go @@ -0,0 +1,674 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.28.1 +// protoc v3.21.8 +// source: cln_plugin.proto + +package cln_plugin + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type HtlcAccepted struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Correlationid uint64 `protobuf:"varint,1,opt,name=correlationid,proto3" json:"correlationid,omitempty"` + Onion *Onion `protobuf:"bytes,2,opt,name=onion,proto3" json:"onion,omitempty"` + Htlc *HtlcOffer `protobuf:"bytes,3,opt,name=htlc,proto3" json:"htlc,omitempty"` +} + +func (x *HtlcAccepted) Reset() { + *x = HtlcAccepted{} + if protoimpl.UnsafeEnabled { + mi := &file_cln_plugin_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *HtlcAccepted) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HtlcAccepted) ProtoMessage() {} + +func (x *HtlcAccepted) ProtoReflect() protoreflect.Message { + mi := &file_cln_plugin_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HtlcAccepted.ProtoReflect.Descriptor instead. +func (*HtlcAccepted) Descriptor() ([]byte, []int) { + return file_cln_plugin_proto_rawDescGZIP(), []int{0} +} + +func (x *HtlcAccepted) GetCorrelationid() uint64 { + if x != nil { + return x.Correlationid + } + return 0 +} + +func (x *HtlcAccepted) GetOnion() *Onion { + if x != nil { + return x.Onion + } + return nil +} + +func (x *HtlcAccepted) GetHtlc() *HtlcOffer { + if x != nil { + return x.Htlc + } + return nil +} + +type Onion struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` + ShortChannelId uint64 `protobuf:"varint,2,opt,name=short_channel_id,json=shortChannelId,proto3" json:"short_channel_id,omitempty"` + ForwardAmountMsat uint64 `protobuf:"varint,3,opt,name=forward_amount_msat,json=forwardAmountMsat,proto3" json:"forward_amount_msat,omitempty"` +} + +func (x *Onion) Reset() { + *x = Onion{} + if protoimpl.UnsafeEnabled { + mi := &file_cln_plugin_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Onion) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Onion) ProtoMessage() {} + +func (x *Onion) ProtoReflect() protoreflect.Message { + mi := &file_cln_plugin_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Onion.ProtoReflect.Descriptor instead. +func (*Onion) Descriptor() ([]byte, []int) { + return file_cln_plugin_proto_rawDescGZIP(), []int{1} +} + +func (x *Onion) GetPayload() []byte { + if x != nil { + return x.Payload + } + return nil +} + +func (x *Onion) GetShortChannelId() uint64 { + if x != nil { + return x.ShortChannelId + } + return 0 +} + +func (x *Onion) GetForwardAmountMsat() uint64 { + if x != nil { + return x.ForwardAmountMsat + } + return 0 +} + +type HtlcOffer struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + AmountMsat uint64 `protobuf:"varint,2,opt,name=amount_msat,json=amountMsat,proto3" json:"amount_msat,omitempty"` + CltvExpiryRelative uint32 `protobuf:"varint,3,opt,name=cltv_expiry_relative,json=cltvExpiryRelative,proto3" json:"cltv_expiry_relative,omitempty"` + CltvExpiry uint32 `protobuf:"varint,4,opt,name=cltv_expiry,json=cltvExpiry,proto3" json:"cltv_expiry,omitempty"` + PaymentHash []byte `protobuf:"bytes,5,opt,name=payment_hash,json=paymentHash,proto3" json:"payment_hash,omitempty"` +} + +func (x *HtlcOffer) Reset() { + *x = HtlcOffer{} + if protoimpl.UnsafeEnabled { + mi := &file_cln_plugin_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *HtlcOffer) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HtlcOffer) ProtoMessage() {} + +func (x *HtlcOffer) ProtoReflect() protoreflect.Message { + mi := &file_cln_plugin_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HtlcOffer.ProtoReflect.Descriptor instead. +func (*HtlcOffer) Descriptor() ([]byte, []int) { + return file_cln_plugin_proto_rawDescGZIP(), []int{2} +} + +func (x *HtlcOffer) GetAmountMsat() uint64 { + if x != nil { + return x.AmountMsat + } + return 0 +} + +func (x *HtlcOffer) GetCltvExpiryRelative() uint32 { + if x != nil { + return x.CltvExpiryRelative + } + return 0 +} + +func (x *HtlcOffer) GetCltvExpiry() uint32 { + if x != nil { + return x.CltvExpiry + } + return 0 +} + +func (x *HtlcOffer) GetPaymentHash() []byte { + if x != nil { + return x.PaymentHash + } + return nil +} + +type HtlcResolution struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Correlationid uint64 `protobuf:"varint,1,opt,name=correlationid,proto3" json:"correlationid,omitempty"` + // Types that are assignable to Outcome: + // *HtlcResolution_Fail + // *HtlcResolution_Continue + // *HtlcResolution_ContinueWith + Outcome isHtlcResolution_Outcome `protobuf_oneof:"outcome"` +} + +func (x *HtlcResolution) Reset() { + *x = HtlcResolution{} + if protoimpl.UnsafeEnabled { + mi := &file_cln_plugin_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *HtlcResolution) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HtlcResolution) ProtoMessage() {} + +func (x *HtlcResolution) ProtoReflect() protoreflect.Message { + mi := &file_cln_plugin_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HtlcResolution.ProtoReflect.Descriptor instead. +func (*HtlcResolution) Descriptor() ([]byte, []int) { + return file_cln_plugin_proto_rawDescGZIP(), []int{3} +} + +func (x *HtlcResolution) GetCorrelationid() uint64 { + if x != nil { + return x.Correlationid + } + return 0 +} + +func (m *HtlcResolution) GetOutcome() isHtlcResolution_Outcome { + if m != nil { + return m.Outcome + } + return nil +} + +func (x *HtlcResolution) GetFail() *HtlcFail { + if x, ok := x.GetOutcome().(*HtlcResolution_Fail); ok { + return x.Fail + } + return nil +} + +func (x *HtlcResolution) GetContinue() *HtlcContinue { + if x, ok := x.GetOutcome().(*HtlcResolution_Continue); ok { + return x.Continue + } + return nil +} + +func (x *HtlcResolution) GetContinueWith() *HtlcContinueWith { + if x, ok := x.GetOutcome().(*HtlcResolution_ContinueWith); ok { + return x.ContinueWith + } + return nil +} + +type isHtlcResolution_Outcome interface { + isHtlcResolution_Outcome() +} + +type HtlcResolution_Fail struct { + Fail *HtlcFail `protobuf:"bytes,2,opt,name=fail,proto3,oneof"` +} + +type HtlcResolution_Continue struct { + Continue *HtlcContinue `protobuf:"bytes,3,opt,name=continue,proto3,oneof"` +} + +type HtlcResolution_ContinueWith struct { + ContinueWith *HtlcContinueWith `protobuf:"bytes,4,opt,name=continue_with,json=continueWith,proto3,oneof"` +} + +func (*HtlcResolution_Fail) isHtlcResolution_Outcome() {} + +func (*HtlcResolution_Continue) isHtlcResolution_Outcome() {} + +func (*HtlcResolution_ContinueWith) isHtlcResolution_Outcome() {} + +type HtlcFail struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + FailureMessage []byte `protobuf:"bytes,1,opt,name=failure_message,json=failureMessage,proto3" json:"failure_message,omitempty"` +} + +func (x *HtlcFail) Reset() { + *x = HtlcFail{} + if protoimpl.UnsafeEnabled { + mi := &file_cln_plugin_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *HtlcFail) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HtlcFail) ProtoMessage() {} + +func (x *HtlcFail) ProtoReflect() protoreflect.Message { + mi := &file_cln_plugin_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HtlcFail.ProtoReflect.Descriptor instead. +func (*HtlcFail) Descriptor() ([]byte, []int) { + return file_cln_plugin_proto_rawDescGZIP(), []int{4} +} + +func (x *HtlcFail) GetFailureMessage() []byte { + if x != nil { + return x.FailureMessage + } + return nil +} + +type HtlcContinue struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *HtlcContinue) Reset() { + *x = HtlcContinue{} + if protoimpl.UnsafeEnabled { + mi := &file_cln_plugin_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *HtlcContinue) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HtlcContinue) ProtoMessage() {} + +func (x *HtlcContinue) ProtoReflect() protoreflect.Message { + mi := &file_cln_plugin_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HtlcContinue.ProtoReflect.Descriptor instead. +func (*HtlcContinue) Descriptor() ([]byte, []int) { + return file_cln_plugin_proto_rawDescGZIP(), []int{5} +} + +type HtlcContinueWith struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ChannelId []byte `protobuf:"bytes,1,opt,name=channel_id,json=channelId,proto3" json:"channel_id,omitempty"` + Payload []byte `protobuf:"bytes,2,opt,name=payload,proto3" json:"payload,omitempty"` +} + +func (x *HtlcContinueWith) Reset() { + *x = HtlcContinueWith{} + if protoimpl.UnsafeEnabled { + mi := &file_cln_plugin_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *HtlcContinueWith) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HtlcContinueWith) ProtoMessage() {} + +func (x *HtlcContinueWith) ProtoReflect() protoreflect.Message { + mi := &file_cln_plugin_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HtlcContinueWith.ProtoReflect.Descriptor instead. +func (*HtlcContinueWith) Descriptor() ([]byte, []int) { + return file_cln_plugin_proto_rawDescGZIP(), []int{6} +} + +func (x *HtlcContinueWith) GetChannelId() []byte { + if x != nil { + return x.ChannelId + } + return nil +} + +func (x *HtlcContinueWith) GetPayload() []byte { + if x != nil { + return x.Payload + } + return nil +} + +var File_cln_plugin_proto protoreflect.FileDescriptor + +var file_cln_plugin_proto_rawDesc = []byte{ + 0x0a, 0x10, 0x63, 0x6c, 0x6e, 0x5f, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x22, 0x72, 0x0a, 0x0c, 0x48, 0x74, 0x6c, 0x63, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, + 0x65, 0x64, 0x12, 0x24, 0x0a, 0x0d, 0x63, 0x6f, 0x72, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0d, 0x63, 0x6f, 0x72, 0x72, 0x65, + 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x69, 0x64, 0x12, 0x1c, 0x0a, 0x05, 0x6f, 0x6e, 0x69, 0x6f, + 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x06, 0x2e, 0x4f, 0x6e, 0x69, 0x6f, 0x6e, 0x52, + 0x05, 0x6f, 0x6e, 0x69, 0x6f, 0x6e, 0x12, 0x1e, 0x0a, 0x04, 0x68, 0x74, 0x6c, 0x63, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0a, 0x2e, 0x48, 0x74, 0x6c, 0x63, 0x4f, 0x66, 0x66, 0x65, 0x72, + 0x52, 0x04, 0x68, 0x74, 0x6c, 0x63, 0x22, 0x7b, 0x0a, 0x05, 0x4f, 0x6e, 0x69, 0x6f, 0x6e, 0x12, + 0x18, 0x0a, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x28, 0x0a, 0x10, 0x73, 0x68, 0x6f, + 0x72, 0x74, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x04, 0x52, 0x0e, 0x73, 0x68, 0x6f, 0x72, 0x74, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, + 0x6c, 0x49, 0x64, 0x12, 0x2e, 0x0a, 0x13, 0x66, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x5f, 0x61, + 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, + 0x52, 0x11, 0x66, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x4d, + 0x73, 0x61, 0x74, 0x22, 0xa2, 0x01, 0x0a, 0x09, 0x48, 0x74, 0x6c, 0x63, 0x4f, 0x66, 0x66, 0x65, + 0x72, 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x4d, 0x73, + 0x61, 0x74, 0x12, 0x30, 0x0a, 0x14, 0x63, 0x6c, 0x74, 0x76, 0x5f, 0x65, 0x78, 0x70, 0x69, 0x72, + 0x79, 0x5f, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x76, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, + 0x52, 0x12, 0x63, 0x6c, 0x74, 0x76, 0x45, 0x78, 0x70, 0x69, 0x72, 0x79, 0x52, 0x65, 0x6c, 0x61, + 0x74, 0x69, 0x76, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x6c, 0x74, 0x76, 0x5f, 0x65, 0x78, 0x70, + 0x69, 0x72, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x63, 0x6c, 0x74, 0x76, 0x45, + 0x78, 0x70, 0x69, 0x72, 0x79, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, + 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x70, 0x61, 0x79, + 0x6d, 0x65, 0x6e, 0x74, 0x48, 0x61, 0x73, 0x68, 0x22, 0xc9, 0x01, 0x0a, 0x0e, 0x48, 0x74, 0x6c, + 0x63, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x24, 0x0a, 0x0d, 0x63, + 0x6f, 0x72, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x04, 0x52, 0x0d, 0x63, 0x6f, 0x72, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x69, + 0x64, 0x12, 0x1f, 0x0a, 0x04, 0x66, 0x61, 0x69, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x09, 0x2e, 0x48, 0x74, 0x6c, 0x63, 0x46, 0x61, 0x69, 0x6c, 0x48, 0x00, 0x52, 0x04, 0x66, 0x61, + 0x69, 0x6c, 0x12, 0x2b, 0x0a, 0x08, 0x63, 0x6f, 0x6e, 0x74, 0x69, 0x6e, 0x75, 0x65, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x48, 0x74, 0x6c, 0x63, 0x43, 0x6f, 0x6e, 0x74, 0x69, + 0x6e, 0x75, 0x65, 0x48, 0x00, 0x52, 0x08, 0x63, 0x6f, 0x6e, 0x74, 0x69, 0x6e, 0x75, 0x65, 0x12, + 0x38, 0x0a, 0x0d, 0x63, 0x6f, 0x6e, 0x74, 0x69, 0x6e, 0x75, 0x65, 0x5f, 0x77, 0x69, 0x74, 0x68, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x48, 0x74, 0x6c, 0x63, 0x43, 0x6f, 0x6e, + 0x74, 0x69, 0x6e, 0x75, 0x65, 0x57, 0x69, 0x74, 0x68, 0x48, 0x00, 0x52, 0x0c, 0x63, 0x6f, 0x6e, + 0x74, 0x69, 0x6e, 0x75, 0x65, 0x57, 0x69, 0x74, 0x68, 0x42, 0x09, 0x0a, 0x07, 0x6f, 0x75, 0x74, + 0x63, 0x6f, 0x6d, 0x65, 0x22, 0x33, 0x0a, 0x08, 0x48, 0x74, 0x6c, 0x63, 0x46, 0x61, 0x69, 0x6c, + 0x12, 0x27, 0x0a, 0x0f, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x5f, 0x6d, 0x65, 0x73, 0x73, + 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0e, 0x66, 0x61, 0x69, 0x6c, 0x75, + 0x72, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x0e, 0x0a, 0x0c, 0x48, 0x74, 0x6c, + 0x63, 0x43, 0x6f, 0x6e, 0x74, 0x69, 0x6e, 0x75, 0x65, 0x22, 0x4b, 0x0a, 0x10, 0x48, 0x74, 0x6c, + 0x63, 0x43, 0x6f, 0x6e, 0x74, 0x69, 0x6e, 0x75, 0x65, 0x57, 0x69, 0x74, 0x68, 0x12, 0x1d, 0x0a, + 0x0a, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x09, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, + 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x70, + 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x32, 0x3d, 0x0a, 0x09, 0x43, 0x6c, 0x6e, 0x50, 0x6c, 0x75, + 0x67, 0x69, 0x6e, 0x12, 0x30, 0x0a, 0x0a, 0x48, 0x74, 0x6c, 0x63, 0x53, 0x74, 0x72, 0x65, 0x61, + 0x6d, 0x12, 0x0f, 0x2e, 0x48, 0x74, 0x6c, 0x63, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, + 0x6f, 0x6e, 0x1a, 0x0d, 0x2e, 0x48, 0x74, 0x6c, 0x63, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, + 0x64, 0x28, 0x01, 0x30, 0x01, 0x42, 0x22, 0x5a, 0x20, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, + 0x63, 0x6f, 0x6d, 0x2f, 0x62, 0x72, 0x65, 0x65, 0x7a, 0x2f, 0x6c, 0x73, 0x70, 0x64, 0x2f, 0x63, + 0x6c, 0x6e, 0x5f, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x33, +} + +var ( + file_cln_plugin_proto_rawDescOnce sync.Once + file_cln_plugin_proto_rawDescData = file_cln_plugin_proto_rawDesc +) + +func file_cln_plugin_proto_rawDescGZIP() []byte { + file_cln_plugin_proto_rawDescOnce.Do(func() { + file_cln_plugin_proto_rawDescData = protoimpl.X.CompressGZIP(file_cln_plugin_proto_rawDescData) + }) + return file_cln_plugin_proto_rawDescData +} + +var file_cln_plugin_proto_msgTypes = make([]protoimpl.MessageInfo, 7) +var file_cln_plugin_proto_goTypes = []interface{}{ + (*HtlcAccepted)(nil), // 0: HtlcAccepted + (*Onion)(nil), // 1: Onion + (*HtlcOffer)(nil), // 2: HtlcOffer + (*HtlcResolution)(nil), // 3: HtlcResolution + (*HtlcFail)(nil), // 4: HtlcFail + (*HtlcContinue)(nil), // 5: HtlcContinue + (*HtlcContinueWith)(nil), // 6: HtlcContinueWith +} +var file_cln_plugin_proto_depIdxs = []int32{ + 1, // 0: HtlcAccepted.onion:type_name -> Onion + 2, // 1: HtlcAccepted.htlc:type_name -> HtlcOffer + 4, // 2: HtlcResolution.fail:type_name -> HtlcFail + 5, // 3: HtlcResolution.continue:type_name -> HtlcContinue + 6, // 4: HtlcResolution.continue_with:type_name -> HtlcContinueWith + 3, // 5: ClnPlugin.HtlcStream:input_type -> HtlcResolution + 0, // 6: ClnPlugin.HtlcStream:output_type -> HtlcAccepted + 6, // [6:7] is the sub-list for method output_type + 5, // [5:6] is the sub-list for method input_type + 5, // [5:5] is the sub-list for extension type_name + 5, // [5:5] is the sub-list for extension extendee + 0, // [0:5] is the sub-list for field type_name +} + +func init() { file_cln_plugin_proto_init() } +func file_cln_plugin_proto_init() { + if File_cln_plugin_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_cln_plugin_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*HtlcAccepted); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_cln_plugin_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Onion); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_cln_plugin_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*HtlcOffer); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_cln_plugin_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*HtlcResolution); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_cln_plugin_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*HtlcFail); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_cln_plugin_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*HtlcContinue); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_cln_plugin_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*HtlcContinueWith); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + file_cln_plugin_proto_msgTypes[3].OneofWrappers = []interface{}{ + (*HtlcResolution_Fail)(nil), + (*HtlcResolution_Continue)(nil), + (*HtlcResolution_ContinueWith)(nil), + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_cln_plugin_proto_rawDesc, + NumEnums: 0, + NumMessages: 7, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_cln_plugin_proto_goTypes, + DependencyIndexes: file_cln_plugin_proto_depIdxs, + MessageInfos: file_cln_plugin_proto_msgTypes, + }.Build() + File_cln_plugin_proto = out.File + file_cln_plugin_proto_rawDesc = nil + file_cln_plugin_proto_goTypes = nil + file_cln_plugin_proto_depIdxs = nil +} diff --git a/cln_plugin/cln_plugin_grpc.pb.go b/cln_plugin/cln_plugin_grpc.pb.go new file mode 100644 index 0000000..3d4c9f9 --- /dev/null +++ b/cln_plugin/cln_plugin_grpc.pb.go @@ -0,0 +1,137 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.2.0 +// - protoc v3.21.8 +// source: cln_plugin.proto + +package cln_plugin + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +// ClnPluginClient is the client API for ClnPlugin service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type ClnPluginClient interface { + HtlcStream(ctx context.Context, opts ...grpc.CallOption) (ClnPlugin_HtlcStreamClient, error) +} + +type clnPluginClient struct { + cc grpc.ClientConnInterface +} + +func NewClnPluginClient(cc grpc.ClientConnInterface) ClnPluginClient { + return &clnPluginClient{cc} +} + +func (c *clnPluginClient) HtlcStream(ctx context.Context, opts ...grpc.CallOption) (ClnPlugin_HtlcStreamClient, error) { + stream, err := c.cc.NewStream(ctx, &ClnPlugin_ServiceDesc.Streams[0], "/ClnPlugin/HtlcStream", opts...) + if err != nil { + return nil, err + } + x := &clnPluginHtlcStreamClient{stream} + return x, nil +} + +type ClnPlugin_HtlcStreamClient interface { + Send(*HtlcResolution) error + Recv() (*HtlcAccepted, error) + grpc.ClientStream +} + +type clnPluginHtlcStreamClient struct { + grpc.ClientStream +} + +func (x *clnPluginHtlcStreamClient) Send(m *HtlcResolution) error { + return x.ClientStream.SendMsg(m) +} + +func (x *clnPluginHtlcStreamClient) Recv() (*HtlcAccepted, error) { + m := new(HtlcAccepted) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +// ClnPluginServer is the server API for ClnPlugin service. +// All implementations must embed UnimplementedClnPluginServer +// for forward compatibility +type ClnPluginServer interface { + HtlcStream(ClnPlugin_HtlcStreamServer) error + mustEmbedUnimplementedClnPluginServer() +} + +// UnimplementedClnPluginServer must be embedded to have forward compatible implementations. +type UnimplementedClnPluginServer struct { +} + +func (UnimplementedClnPluginServer) HtlcStream(ClnPlugin_HtlcStreamServer) error { + return status.Errorf(codes.Unimplemented, "method HtlcStream not implemented") +} +func (UnimplementedClnPluginServer) mustEmbedUnimplementedClnPluginServer() {} + +// UnsafeClnPluginServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to ClnPluginServer will +// result in compilation errors. +type UnsafeClnPluginServer interface { + mustEmbedUnimplementedClnPluginServer() +} + +func RegisterClnPluginServer(s grpc.ServiceRegistrar, srv ClnPluginServer) { + s.RegisterService(&ClnPlugin_ServiceDesc, srv) +} + +func _ClnPlugin_HtlcStream_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(ClnPluginServer).HtlcStream(&clnPluginHtlcStreamServer{stream}) +} + +type ClnPlugin_HtlcStreamServer interface { + Send(*HtlcAccepted) error + Recv() (*HtlcResolution, error) + grpc.ServerStream +} + +type clnPluginHtlcStreamServer struct { + grpc.ServerStream +} + +func (x *clnPluginHtlcStreamServer) Send(m *HtlcAccepted) error { + return x.ServerStream.SendMsg(m) +} + +func (x *clnPluginHtlcStreamServer) Recv() (*HtlcResolution, error) { + m := new(HtlcResolution) + if err := x.ServerStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +// ClnPlugin_ServiceDesc is the grpc.ServiceDesc for ClnPlugin service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var ClnPlugin_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "ClnPlugin", + HandlerType: (*ClnPluginServer)(nil), + Methods: []grpc.MethodDesc{}, + Streams: []grpc.StreamDesc{ + { + StreamName: "HtlcStream", + Handler: _ClnPlugin_HtlcStream_Handler, + ServerStreams: true, + ClientStreams: true, + }, + }, + Metadata: "cln_plugin.proto", +} diff --git a/cln_plugin/cmd/main.go b/cln_plugin/cmd/main.go new file mode 100644 index 0000000..5a84e46 --- /dev/null +++ b/cln_plugin/cmd/main.go @@ -0,0 +1,31 @@ +package main + +import ( + "log" + "os" + "os/signal" + "syscall" + + "github.com/breez/lspd/cln_plugin" +) + +func main() { + listen := os.Getenv("LISTEN_ADDRESS") + server := cln_plugin.NewServer(listen) + plugin := cln_plugin.NewClnPlugin(server) + + c := make(chan os.Signal, 1) + signal.Notify(c, os.Interrupt, syscall.SIGINT) + go func() { + sig := <-c + log.Printf("Received stop signal %v. Stopping.", sig) + plugin.Stop() + }() + + err := plugin.Start() + if err == nil { + log.Printf("cln plugin stopped.") + } else { + log.Printf("cln plugin stopped with error: %v", err) + } +} diff --git a/cln_plugin/genproto.sh b/cln_plugin/genproto.sh new file mode 100755 index 0000000..226b0fe --- /dev/null +++ b/cln_plugin/genproto.sh @@ -0,0 +1,5 @@ +#!/bin/bash +SCRIPTDIR=$(dirname $0) +PROTO_ROOT=$SCRIPTDIR/proto + +protoc --go_out=$SCRIPTDIR --go_opt=paths=source_relative --go-grpc_out=$SCRIPTDIR --go-grpc_opt=paths=source_relative -I=$PROTO_ROOT $PROTO_ROOT/* \ No newline at end of file diff --git a/cln_plugin/proto/cln_plugin.proto b/cln_plugin/proto/cln_plugin.proto new file mode 100644 index 0000000..f284d40 --- /dev/null +++ b/cln_plugin/proto/cln_plugin.proto @@ -0,0 +1,46 @@ +syntax = "proto3"; +option go_package="github.com/breez/lspd/cln_plugin"; + +service ClnPlugin { + rpc HtlcStream(stream HtlcResolution) returns (stream HtlcAccepted); +} + +message HtlcAccepted { + uint64 correlationid = 1; + Onion onion = 2; + HtlcOffer htlc = 3; +} + +message Onion { + bytes payload = 1; + uint64 short_channel_id = 2; + uint64 forward_amount_msat = 3; +} + +message HtlcOffer { + uint64 amount_msat = 2; + uint32 cltv_expiry_relative = 3; + uint32 cltv_expiry = 4; + bytes payment_hash = 5; +} + +message HtlcResolution { + uint64 correlationid = 1; + oneof outcome { + HtlcFail fail = 2; + HtlcContinue continue = 3; + HtlcContinueWith continue_with = 4; + } +} + +message HtlcFail { + bytes failure_message = 1; +} + +message HtlcContinue { +} + +message HtlcContinueWith { + bytes channel_id = 1; + bytes payload = 2; +} \ No newline at end of file diff --git a/cln_plugin/sample.env b/cln_plugin/sample.env new file mode 100644 index 0000000..1dc0012 --- /dev/null +++ b/cln_plugin/sample.env @@ -0,0 +1 @@ +LISTEN_ADDRESS=
\ No newline at end of file diff --git a/cln_plugin/server.go b/cln_plugin/server.go new file mode 100644 index 0000000..b677b96 --- /dev/null +++ b/cln_plugin/server.go @@ -0,0 +1,225 @@ +package cln_plugin + +import ( + "fmt" + "io" + "log" + "net" + "sync" + "time" + + grpc "google.golang.org/grpc" +) + +var receiveWaitDelay = time.Millisecond * 200 + +type subscription struct { + stream ClnPlugin_HtlcStreamServer + done chan struct{} +} +type server struct { + ClnPluginServer + listenAddress string + grpcServer *grpc.Server + startMtx sync.Mutex + corrMtx sync.Mutex + subscription *subscription + newSubscriber chan struct{} + done chan struct{} + err chan error + correlations map[uint64]chan *HtlcResolution + index uint64 +} + +func NewServer(listenAddress string) *server { + return &server{ + listenAddress: listenAddress, + newSubscriber: make(chan struct{}, 1), + done: make(chan struct{}), + err: make(chan error, 1), + correlations: make(map[uint64]chan *HtlcResolution), + index: 0, + } +} + +func (s *server) Start() error { + s.startMtx.Lock() + if s.grpcServer != nil { + s.startMtx.Unlock() + return nil + } + + lis, err := net.Listen("tcp", s.listenAddress) + if err != nil { + log.Printf("ERROR Server failed to listen: %v", err) + s.startMtx.Unlock() + return err + } + + s.grpcServer = grpc.NewServer() + s.startMtx.Unlock() + RegisterClnPluginServer(s.grpcServer, s) + + log.Printf("Server starting to listen on %s.", s.listenAddress) + go s.listenHtlcResponses() + return s.grpcServer.Serve(lis) +} + +func (s *server) Stop() { + s.startMtx.Lock() + defer s.startMtx.Unlock() + log.Printf("Server Stop() called.") + if s.grpcServer == nil { + return + } + + close(s.done) + s.grpcServer.Stop() + s.grpcServer = nil +} + +func (s *server) HtlcStream(stream ClnPlugin_HtlcStreamServer) error { + log.Printf("Got HTLC stream subscription request.") + s.startMtx.Lock() + if s.subscription != nil { + s.startMtx.Unlock() + return fmt.Errorf("already subscribed") + } + + sb := &subscription{ + stream: stream, + done: make(chan struct{}), + } + s.subscription = sb + s.newSubscriber <- struct{}{} + s.startMtx.Unlock() + + defer func() { + s.startMtx.Lock() + s.subscription = nil + close(sb.done) + s.startMtx.Unlock() + }() + + go func() { + <-stream.Context().Done() + log.Printf("HtlcStream context is done. Removing subscriber: %v", stream.Context().Err()) + s.startMtx.Lock() + s.subscription = nil + close(sb.done) + s.startMtx.Unlock() + }() + + for { + select { + case <-s.done: + log.Printf("HTLC server signalled done. Return EOF.") + return io.EOF + case err := <-s.err: + log.Printf("HTLC server signalled error: %v", err) + return err + case <-sb.done: + log.Printf("HTLC stream signalled done. Return EOF.") + return io.EOF + } + } +} + +func (s *server) Send(h *HtlcAccepted) *HtlcResolution { + sb := s.subscription + if sb == nil { + log.Printf("No subscribers available. Ignoring HtlcAccepted %+v", h) + return s.defaultResolution() + } + + c := make(chan *HtlcResolution) + s.corrMtx.Lock() + s.index++ + index := s.index + s.correlations[index] = c + s.corrMtx.Unlock() + + h.Correlationid = index + + defer func() { + s.corrMtx.Lock() + delete(s.correlations, index) + s.corrMtx.Unlock() + close(c) + }() + + log.Printf("Sending HtlcAccepted: %+v", h) + err := sb.stream.Send(h) + if err != nil { + // TODO: Close the connection? Reset the subscriber? + log.Printf("Send() errored, Correlationid: %d: %v", index, err) + return s.defaultResolution() + } + + select { + case <-s.done: + log.Printf("Signalled done while waiting for htlc resolution, Correlationid: %d, Ignoring: %+v", index, h) + return s.defaultResolution() + case resolution := <-c: + log.Printf("Got resolution, Correlationid: %d: %+v", index, h) + return resolution + } +} + +func (s *server) recv() *HtlcResolution { + for { + sb := s.subscription + if sb == nil { + log.Printf("Got no subscribers for receive. Waiting for subscriber.") + select { + case <-s.done: + log.Printf("Done signalled, stopping receive.") + return s.defaultResolution() + case <-s.newSubscriber: + log.Printf("New subscription available for receive, continue receive.") + continue + } + } + + r, err := sb.stream.Recv() + if err == nil { + log.Printf("Received HtlcResolution %+v", r) + return r + } + + // TODO: close the subscription?? + log.Printf("Recv() errored, waiting %v: %v", receiveWaitDelay, err) + <-time.After(receiveWaitDelay) + } +} + +func (s *server) listenHtlcResponses() { + for { + select { + case <-s.done: + log.Printf("listenHtlcResponses received done. Stopping listening.") + return + case err := <-s.err: + log.Printf("listenHtlcResponses received error %v. Stopping listening.", err) + return + default: + response := s.recv() + s.corrMtx.Lock() + correlation, ok := s.correlations[response.Correlationid] + s.corrMtx.Unlock() + if ok { + correlation <- response + } else { + log.Printf("Got HTLC resolution that could not be correlated: %+v", response) + } + } + } +} + +func (s *server) defaultResolution() *HtlcResolution { + return &HtlcResolution{ + Outcome: &HtlcResolution_Continue{ + Continue: &HtlcContinue{}, + }, + } +} From 5922b7e73d6ac8d937266f3ee998f3a89aa2d50c Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Fri, 23 Dec 2022 18:22:59 +0100 Subject: [PATCH 125/214] use cln plugin wrapper in interceptor --- cln_client.go | 5 +- cln_interceptor.go | 250 +++++++++++++++++++++++++---------------- go.mod | 4 +- itest/cln_lspd_node.go | 104 ++++++++++++++++- sample.env | 3 + 5 files changed, 265 insertions(+), 101 deletions(-) diff --git a/cln_client.go b/cln_client.go index cc9f088..c59b50e 100644 --- a/cln_client.go +++ b/cln_client.go @@ -4,6 +4,7 @@ import ( "encoding/hex" "fmt" "log" + "os" "github.com/breez/lspd/basetypes" "github.com/btcsuite/btcd/chaincfg/chainhash" @@ -23,7 +24,9 @@ var ( CLOSED_STATUSES = []string{"CLOSED"} ) -func NewClnClient(rpcFile string, lightningDir string) *ClnClient { +func NewClnClient() *ClnClient { + rpcFile := os.Getenv("CLN_SOCKET_NAME") + lightningDir := os.Getenv("CLN_SOCKET_DIR") client := glightning.NewLightning() client.SetTimeout(60) client.StartUp(rpcFile, lightningDir) diff --git a/cln_interceptor.go b/cln_interceptor.go index 02f6903..ab082fd 100644 --- a/cln_interceptor.go +++ b/cln_interceptor.go @@ -2,56 +2,159 @@ package main import ( "bytes" - "encoding/hex" + "context" "fmt" "io" "log" "os" "sync" + "time" + "github.com/breez/lspd/cln_plugin" sphinx "github.com/lightningnetwork/lightning-onion" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/record" "github.com/lightningnetwork/lnd/tlv" - "github.com/niftynei/glightning/glightning" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/credentials/insecure" + "google.golang.org/grpc/status" ) type ClnHtlcInterceptor struct { - client *ClnClient - plugin *glightning.Plugin - initWg sync.WaitGroup + pluginAddress string + client *ClnClient + pluginClient cln_plugin.ClnPluginClient + initWg sync.WaitGroup + doneWg sync.WaitGroup + ctx context.Context + cancel context.CancelFunc } func NewClnHtlcInterceptor() *ClnHtlcInterceptor { - i := &ClnHtlcInterceptor{} + i := &ClnHtlcInterceptor{ + pluginAddress: os.Getenv("CLN_PLUGIN_ADDRESS"), + client: NewClnClient(), + } i.initWg.Add(1) return i } func (i *ClnHtlcInterceptor) Start() error { - //c-lightning plugin initiate - plugin := glightning.NewPlugin(i.onInit) - i.plugin = plugin - plugin.RegisterHooks(&glightning.Hooks{ - HtlcAccepted: i.OnHtlcAccepted, - }) - - err := plugin.Start(os.Stdin, os.Stdout) + ctx, cancel := context.WithCancel(context.Background()) + log.Printf("Dialing cln plugin on '%s'", i.pluginAddress) + conn, err := grpc.DialContext(ctx, i.pluginAddress, grpc.WithTransportCredentials(insecure.NewCredentials())) if err != nil { - log.Printf("Plugin error: %v", err) + log.Printf("grpc.Dial error: %v", err) + cancel() return err } - return nil + i.pluginClient = cln_plugin.NewClnPluginClient(conn) + i.ctx = ctx + i.cancel = cancel + return i.intercept() +} + +func (i *ClnHtlcInterceptor) intercept() error { + inited := false + + defer func() { + if !inited { + i.initWg.Done() + } + log.Printf("CLN intercept(): stopping. Waiting for in-progress interceptions to complete.") + i.doneWg.Wait() + }() + + for { + if i.ctx.Err() != nil { + return i.ctx.Err() + } + + log.Printf("Connecting CLN HTLC interceptor.") + interceptorClient, err := i.pluginClient.HtlcStream(i.ctx) + if err != nil { + log.Printf("pluginClient.HtlcStream(): %v", err) + <-time.After(time.Second) + continue + } + + for { + if i.ctx.Err() != nil { + return i.ctx.Err() + } + + if !inited { + inited = true + i.initWg.Done() + } + + request, err := interceptorClient.Recv() + if err != nil { + // If it is just the error result of the context cancellation + // the we exit silently. + status, ok := status.FromError(err) + if ok && status.Code() == codes.Canceled { + log.Printf("Got code canceled. Break.") + break + } + + // Otherwise it an unexpected error, we fail the test. + log.Printf("unexpected error in interceptor.Recv() %v", err) + break + } + + log.Printf("correlationid: %v\nhtlc: %v\nchanID: %v\nincoming amount: %v\noutgoing amount: %v\nincoming expiry: %v\noutgoing expiry: %v\npaymentHash: %v\nonionBlob: %v\n\n", + request.Correlationid, + request.Htlc, + request.Onion.ShortChannelId, + request.Htlc.AmountMsat, //with fees + request.Onion.ForwardAmountMsat, + request.Htlc.CltvExpiryRelative, + request.Htlc.CltvExpiry, + request.Htlc.PaymentHash, + request, + ) + + i.doneWg.Add(1) + go func() { + interceptResult := intercept(request.Htlc.PaymentHash, request.Onion.ForwardAmountMsat, request.Htlc.CltvExpiry) + switch interceptResult.action { + case INTERCEPT_RESUME_WITH_ONION: + interceptorClient.Send(i.resumeWithOnion(request, interceptResult)) + case INTERCEPT_FAIL_HTLC_WITH_CODE: + interceptorClient.Send(&cln_plugin.HtlcResolution{ + Correlationid: request.Correlationid, + Outcome: &cln_plugin.HtlcResolution_Fail{ + Fail: &cln_plugin.HtlcFail{ + FailureMessage: i.mapFailureCode(interceptResult.failureCode), + }, + }, + }) + case INTERCEPT_RESUME: + fallthrough + default: + interceptorClient.Send(&cln_plugin.HtlcResolution{ + Correlationid: request.Correlationid, + Outcome: &cln_plugin.HtlcResolution_Continue{ + Continue: &cln_plugin.HtlcContinue{}, + }, + }) + } + + i.doneWg.Done() + }() + } + + <-time.After(time.Second) + } } func (i *ClnHtlcInterceptor) Stop() error { - plugin := i.plugin - if plugin != nil { - plugin.Stop() - } - + i.cancel() + i.doneWg.Wait() return nil } @@ -60,98 +163,57 @@ func (i *ClnHtlcInterceptor) WaitStarted() LightningClient { return i.client } -func (i *ClnHtlcInterceptor) onInit(plugin *glightning.Plugin, options map[string]glightning.Option, config *glightning.Config) { - log.Printf("successfully init'd! %v\n", config.RpcFile) - - //lightning server - clientcln := glightning.NewLightning() - clientcln.SetTimeout(60) - clientcln.StartUp(config.RpcFile, config.LightningDir) - - i.client = &ClnClient{ - client: clientcln, - } - - log.Printf("successfull clientcln.StartUp") - i.initWg.Done() -} - -func (i *ClnHtlcInterceptor) OnHtlcAccepted(event *glightning.HtlcAcceptedEvent) (*glightning.HtlcAcceptedResponse, error) { - log.Printf("htlc_accepted called\n") - onion := event.Onion - - log.Printf("htlc: %v\nchanID: %v\nincoming amount: %v\noutgoing amount: %v\nincoming expiry: %v\noutgoing expiry: %v\npaymentHash: %v\nonionBlob: %v\n\n", - event.Htlc, - onion.ShortChannelId, - event.Htlc.AmountMilliSatoshi, //with fees - onion.ForwardAmount, - event.Htlc.CltvExpiryRelative, - event.Htlc.CltvExpiry, - event.Htlc.PaymentHash, - onion, - ) - - // fail htlc in case payment hash is not valid. - paymentHashBytes, err := hex.DecodeString(event.Htlc.PaymentHash) - if err != nil { - log.Printf("hex.DecodeString(%v) error: %v", event.Htlc.PaymentHash, err) - return event.Fail(i.mapFailureCode(FAILURE_TEMPORARY_CHANNEL_FAILURE)), nil - } - - interceptResult := intercept(paymentHashBytes, onion.ForwardAmount, uint32(event.Htlc.CltvExpiry)) - switch interceptResult.action { - case INTERCEPT_RESUME_WITH_ONION: - return i.resumeWithOnion(event, interceptResult), nil - case INTERCEPT_FAIL_HTLC_WITH_CODE: - return event.Fail(i.mapFailureCode(interceptResult.failureCode)), nil - case INTERCEPT_RESUME: - fallthrough - default: - return event.Continue(), nil - } -} - -func (i *ClnHtlcInterceptor) resumeWithOnion(event *glightning.HtlcAcceptedEvent, interceptResult interceptResult) *glightning.HtlcAcceptedResponse { +func (i *ClnHtlcInterceptor) resumeWithOnion(request *cln_plugin.HtlcAccepted, interceptResult interceptResult) *cln_plugin.HtlcResolution { //decoding and encoding onion with alias in type 6 record. - newPayload, err := encodePayloadWithNextHop(event.Onion.Payload, interceptResult.channelId) + newPayload, err := encodePayloadWithNextHop(request.Onion.Payload, interceptResult.channelId) if err != nil { log.Printf("encodePayloadWithNextHop error: %v", err) - return event.Fail(i.mapFailureCode(FAILURE_TEMPORARY_CHANNEL_FAILURE)) + return &cln_plugin.HtlcResolution{ + Correlationid: request.Correlationid, + Outcome: &cln_plugin.HtlcResolution_Fail{ + Fail: &cln_plugin.HtlcFail{ + FailureMessage: i.mapFailureCode(FAILURE_TEMPORARY_CHANNEL_FAILURE), + }, + }, + } } chanId := lnwire.NewChanIDFromOutPoint(interceptResult.channelPoint) log.Printf("forwarding htlc to the destination node and a new private channel was opened") - return event.ContinueWith(chanId.String(), newPayload) + return &cln_plugin.HtlcResolution{ + Correlationid: request.Correlationid, + Outcome: &cln_plugin.HtlcResolution_ContinueWith{ + ContinueWith: &cln_plugin.HtlcContinueWith{ + ChannelId: chanId[:], + Payload: newPayload, + }, + }, + } } -func encodePayloadWithNextHop(payloadHex string, channelId uint64) (string, error) { - payload, err := hex.DecodeString(payloadHex) - if err != nil { - log.Printf("failed to decode types. error: %v", err) - return "", err - } +func encodePayloadWithNextHop(payload []byte, channelId uint64) ([]byte, error) { bufReader := bytes.NewBuffer(payload) var b [8]byte varInt, err := sphinx.ReadVarInt(bufReader, &b) if err != nil { - return "", fmt.Errorf("failed to read payload length %v: %v", payloadHex, err) + return nil, fmt.Errorf("failed to read payload length %x: %v", payload, err) } innerPayload := make([]byte, varInt) if _, err := io.ReadFull(bufReader, innerPayload[:]); err != nil { - return "", fmt.Errorf("failed to decode payload %x: %v", innerPayload[:], err) + return nil, fmt.Errorf("failed to decode payload %x: %v", innerPayload[:], err) } s, _ := tlv.NewStream() tlvMap, err := s.DecodeWithParsedTypes(bytes.NewReader(innerPayload)) if err != nil { - return "", fmt.Errorf("DecodeWithParsedTypes failed for %x: %v", innerPayload[:], err) + return nil, fmt.Errorf("DecodeWithParsedTypes failed for %x: %v", innerPayload[:], err) } tt := record.NewNextHopIDRecord(&channelId) buf := bytes.NewBuffer([]byte{}) if err := tt.Encode(buf); err != nil { - return "", fmt.Errorf("failed to encode nexthop %x: %v", innerPayload[:], err) + return nil, fmt.Errorf("failed to encode nexthop %x: %v", innerPayload[:], err) } uTlvMap := make(map[uint64][]byte) @@ -165,26 +227,26 @@ func encodePayloadWithNextHop(payloadHex string, channelId uint64) (string, erro tlvRecords := tlv.MapToRecords(uTlvMap) s, err = tlv.NewStream(tlvRecords...) if err != nil { - return "", fmt.Errorf("tlv.NewStream(%x) error: %v", tlvRecords, err) + return nil, fmt.Errorf("tlv.NewStream(%x) error: %v", tlvRecords, err) } var newPayloadBuf bytes.Buffer err = s.Encode(&newPayloadBuf) if err != nil { - return "", fmt.Errorf("encode error: %v", err) + return nil, fmt.Errorf("encode error: %v", err) } - return hex.EncodeToString(newPayloadBuf.Bytes()), nil + return newPayloadBuf.Bytes(), nil } -func (i *ClnHtlcInterceptor) mapFailureCode(original interceptFailureCode) string { +func (i *ClnHtlcInterceptor) mapFailureCode(original interceptFailureCode) []byte { switch original { case FAILURE_TEMPORARY_CHANNEL_FAILURE: - return "1007" + return []byte{0x10, 0x07} case FAILURE_TEMPORARY_NODE_FAILURE: - return "2002" + return []byte{0x20, 0x02} case FAILURE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS: - return "400F" + return []byte{0x40, 0x0F} default: log.Printf("Unknown failure code %v, default to temporary channel failure.", original) - return "1007" // temporary channel failure + return []byte{0x10, 0x07} // temporary channel failure } } diff --git a/go.mod b/go.mod index d667517..65f21e4 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.19 require ( github.com/aws/aws-sdk-go v1.30.20 - github.com/breez/lntest v0.0.16 + github.com/breez/lntest v0.0.17 github.com/btcsuite/btcd v0.23.3 github.com/btcsuite/btcd/btcec/v2 v2.2.1 github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 @@ -163,7 +163,7 @@ require ( golang.org/x/text v0.4.0 // indirect golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba // indirect google.golang.org/genproto v0.0.0-20210617175327-b9e0b3197ced // indirect - google.golang.org/protobuf v1.27.1 // indirect + google.golang.org/protobuf v1.27.1 gopkg.in/errgo.v1 v1.0.1 // indirect gopkg.in/macaroon-bakery.v2 v2.0.1 // indirect gopkg.in/macaroon.v2 v2.0.0 // indirect diff --git a/itest/cln_lspd_node.go b/itest/cln_lspd_node.go index 8290fdb..cdbfdc8 100644 --- a/itest/cln_lspd_node.go +++ b/itest/cln_lspd_node.go @@ -1,7 +1,12 @@ package itest import ( + "flag" "fmt" + "log" + "os" + "os/exec" + "path/filepath" "sync" "github.com/breez/lntest" @@ -12,28 +17,42 @@ import ( "google.golang.org/grpc/credentials/insecure" ) +var clnPluginExec = flag.String( + "clnpluginexec", "", "full path to cln plugin wrapper binary", +) + type ClnLspNode struct { harness *lntest.TestHarness lightningNode *lntest.ClnNode lspBase *lspBase + logFilePath string runtime *clnLspNodeRuntime isInitialized bool mtx sync.Mutex + pluginBinary string + pluginFile string + pluginAddress string } type clnLspNodeRuntime struct { + logFile *os.File + cmd *exec.Cmd rpc lspd.ChannelOpenerClient cleanups []*lntest.Cleanup } func NewClnLspdNode(h *lntest.TestHarness, m *lntest.Miner, name string) LspNode { - lspbase, err := newLspd(h, name, "RUN_CLN=true") + scriptDir := h.GetDirectory("lspd") + pluginFile := filepath.Join(scriptDir, "htlc.sh") + pluginBinary := *clnPluginExec + pluginPort, err := lntest.GetPort() if err != nil { - h.T.Fatalf("failed to initialize lspd") + h.T.Fatalf("failed to get port for the htlc interceptor plugin.") } + pluginAddress := fmt.Sprintf("127.0.0.1:%d", pluginPort) args := []string{ - fmt.Sprintf("--plugin=%s", lspbase.scriptFilePath), + fmt.Sprintf("--plugin=%s", pluginFile), fmt.Sprintf("--fee-base=%d", lspBaseFeeMsat), fmt.Sprintf("--fee-per-satoshi=%d", lspFeeRatePpm), fmt.Sprintf("--cltv-delta=%d", lspCltvDelta), @@ -41,11 +60,27 @@ func NewClnLspdNode(h *lntest.TestHarness, m *lntest.Miner, name string) LspNode "--dev-allowdustreserve=true", } lightningNode := lntest.NewClnNode(h, m, name, args...) + lspbase, err := newLspd(h, name, + "RUN_CLN=true", + fmt.Sprintf("CLN_PLUGIN_ADDRESS=%s", pluginAddress), + fmt.Sprintf("CLN_SOCKET_DIR=%s", lightningNode.SocketDir()), + fmt.Sprintf("CLN_SOCKET_NAME=%s", lightningNode.SocketFile()), + ) + if err != nil { + h.T.Fatalf("failed to initialize lspd") + } + + logFilePath := filepath.Join(scriptDir, "lspd.log") + h.RegisterLogfile(logFilePath, fmt.Sprintf("lspd-%s", name)) lspNode := &ClnLspNode{ harness: h, lightningNode: lightningNode, + logFilePath: logFilePath, lspBase: lspbase, + pluginBinary: pluginBinary, + pluginFile: pluginFile, + pluginAddress: pluginAddress, } h.AddStoppable(lspNode) @@ -67,6 +102,16 @@ func (c *ClnLspNode) Start() { Name: fmt.Sprintf("%s: lsp base", c.lspBase.name), Fn: c.lspBase.Stop, }) + + pluginContent := fmt.Sprintf(`#!/bin/bash +export LISTEN_ADDRESS=%s +%s`, c.pluginAddress, c.pluginBinary) + + err = os.WriteFile(c.pluginFile, []byte(pluginContent), 0755) + if err != nil { + lntest.PerformCleanup(cleanups) + c.harness.T.Fatalf("failed create lsp cln plugin file: %v", err) + } } c.lightningNode.Start() @@ -74,6 +119,51 @@ func (c *ClnLspNode) Start() { Name: fmt.Sprintf("%s: lightning node", c.lspBase.name), Fn: c.lightningNode.Stop, }) + + cmd := exec.CommandContext(c.harness.Ctx, c.lspBase.scriptFilePath) + logFile, err := os.Create(c.logFilePath) + if err != nil { + lntest.PerformCleanup(cleanups) + c.harness.T.Fatalf("failed create lsp logfile: %v", err) + } + cleanups = append(cleanups, &lntest.Cleanup{ + Name: fmt.Sprintf("%s: logfile", c.lspBase.name), + Fn: logFile.Close, + }) + + cmd.Stdout = logFile + cmd.Stderr = logFile + + log.Printf("%s: starting lspd %s", c.lspBase.name, c.lspBase.scriptFilePath) + err = cmd.Start() + if err != nil { + lntest.PerformCleanup(cleanups) + c.harness.T.Fatalf("failed to start lspd: %v", err) + } + cleanups = append(cleanups, &lntest.Cleanup{ + Name: fmt.Sprintf("%s: cmd", c.lspBase.name), + Fn: func() error { + proc := cmd.Process + if proc == nil { + return nil + } + + proc.Kill() + + log.Printf("About to wait for lspd to exit") + status, err := proc.Wait() + if err != nil { + log.Printf("waiting for lspd process error: %v, status: %v", err, status) + } + err = cmd.Wait() + if err != nil { + log.Printf("waiting for lspd cmd error: %v", err) + } + + return nil + }, + }) + conn, err := grpc.Dial( c.lspBase.grpcAddress, grpc.WithTransportCredentials(insecure.NewCredentials()), @@ -81,11 +171,17 @@ func (c *ClnLspNode) Start() { ) if err != nil { lntest.PerformCleanup(cleanups) - c.harness.T.Fatalf("%s: failed to create grpc connection: %v", c.lspBase.name, err) + c.harness.T.Fatalf("failed to create grpc connection: %v", err) } + cleanups = append(cleanups, &lntest.Cleanup{ + Name: fmt.Sprintf("%s: grpc conn", c.lspBase.name), + Fn: conn.Close, + }) client := lspd.NewChannelOpenerClient(conn) c.runtime = &clnLspNodeRuntime{ + logFile: logFile, + cmd: cmd, rpc: client, cleanups: cleanups, } diff --git a/sample.env b/sample.env index 901d9e0..3dfb15f 100644 --- a/sample.env +++ b/sample.env @@ -30,4 +30,7 @@ LND_MACAROON_HEX= RUN_LND=true # CLN specific environment variables +CLN_PLUGIN_ADDRESS=
+CLN_SOCKET_DIR= +CLN_SOCKET_NAME= RUN_CLN=true From 6e673d2be45a5cbe3142b8fcc744f886e2be9a49 Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Fri, 23 Dec 2022 18:23:38 +0100 Subject: [PATCH 126/214] more efficient cleanup, lessons learned --- forwarding_history.go | 32 ++++++++++++++++++++------- itest/lnd_lspd_node.go | 19 ++++++++++------ lnd_interceptor.go | 49 ++++++++++++++++++++++++++---------------- main.go | 11 ++++++++++ 4 files changed, 79 insertions(+), 32 deletions(-) diff --git a/forwarding_history.go b/forwarding_history.go index 56c566d..e45166d 100644 --- a/forwarding_history.go +++ b/forwarding_history.go @@ -36,19 +36,25 @@ func (cfe *copyFromEvents) Err() error { return cfe.err } -func channelsSynchronize(client *LndClient) { +func channelsSynchronize(ctx context.Context, client *LndClient) { lastSync := time.Now().Add(-6 * time.Minute) for { - cancellableCtx, cancel := context.WithCancel(context.Background()) - stream, err := client.chainNotifierClient.RegisterBlockEpochNtfn(cancellableCtx, &chainrpc.BlockEpoch{}) + if ctx.Err() != nil { + return + } + + stream, err := client.chainNotifierClient.RegisterBlockEpochNtfn(ctx, &chainrpc.BlockEpoch{}) if err != nil { log.Printf("chainNotifierClient.RegisterBlockEpochNtfn(): %v", err) - cancel() <-time.After(time.Second) continue } for { + if ctx.Err() != nil { + return + } + _, err := stream.Recv() if err != nil { log.Printf("stream.Recv: %v", err) @@ -56,13 +62,16 @@ func channelsSynchronize(client *LndClient) { break } if lastSync.Add(5 * time.Minute).Before(time.Now()) { - <-time.After(30 * time.Second) + select { + case <-ctx.Done(): + return + case <-time.After(1 * time.Minute): + } err = channelsSynchronizeOnce(client) lastSync = time.Now() log.Printf("channelsSynchronizeOnce() err: %v", err) } } - cancel() } } @@ -99,11 +108,18 @@ func channelsSynchronizeOnce(client *LndClient) error { return nil } -func forwardingHistorySynchronize(client *LndClient) { +func forwardingHistorySynchronize(ctx context.Context, client *LndClient) { for { + if ctx.Err() != nil { + return + } + err := forwardingHistorySynchronizeOnce(client) log.Printf("forwardingHistorySynchronizeOnce() err: %v", err) - <-time.After(1 * time.Minute) + select { + case <-time.After(1 * time.Minute): + case <-ctx.Done(): + } } } diff --git a/itest/lnd_lspd_node.go b/itest/lnd_lspd_node.go index 7fa3385..e096361 100644 --- a/itest/lnd_lspd_node.go +++ b/itest/lnd_lspd_node.go @@ -6,7 +6,6 @@ import ( "os" "os/exec" "path/filepath" - "runtime" "strings" "sync" @@ -153,12 +152,20 @@ func (c *LndLspNode) Start() { Name: fmt.Sprintf("%s: cmd", c.lspBase.name), Fn: func() error { proc := cmd.Process - if proc != nil { - if runtime.GOOS == "windows" { - return proc.Signal(os.Kill) - } + if proc == nil { + return nil + } - return proc.Signal(os.Interrupt) + proc.Kill() + + log.Printf("About to wait for lspd to exit") + status, err := proc.Wait() + if err != nil { + log.Printf("waiting for lspd process error: %v, status: %v", err, status) + } + err = cmd.Wait() + if err != nil { + log.Printf("waiting for lspd cmd error: %v", err) } return nil diff --git a/lnd_interceptor.go b/lnd_interceptor.go index 85f2ee0..a3775c4 100644 --- a/lnd_interceptor.go +++ b/lnd_interceptor.go @@ -14,10 +14,11 @@ import ( ) type LndHtlcInterceptor struct { - client *LndClient - stopRequested bool - initWg sync.WaitGroup - doneWg sync.WaitGroup + client *LndClient + initWg sync.WaitGroup + doneWg sync.WaitGroup + ctx context.Context + cancel context.CancelFunc } func NewLndHtlcInterceptor() *LndHtlcInterceptor { @@ -31,14 +32,18 @@ func NewLndHtlcInterceptor() *LndHtlcInterceptor { } func (i *LndHtlcInterceptor) Start() error { - go forwardingHistorySynchronize(i.client) - go channelsSynchronize(i.client) - i.initWg.Done() + ctx, cancel := context.WithCancel(context.Background()) + i.ctx = ctx + i.cancel = cancel + go forwardingHistorySynchronize(ctx, i.client) + go channelsSynchronize(ctx, i.client) + return i.intercept() } func (i *LndHtlcInterceptor) Stop() error { - i.stopRequested = true + i.cancel() + i.doneWg.Wait() return nil } @@ -48,44 +53,53 @@ func (i *LndHtlcInterceptor) WaitStarted() LightningClient { } func (i *LndHtlcInterceptor) intercept() error { + inited := false defer func() { + if !inited { + i.initWg.Done() + } log.Printf("LND intercept(): stopping. Waiting for in-progress interceptions to complete.") i.doneWg.Wait() }() for { - if i.stopRequested { - return nil + if i.ctx.Err() != nil { + return i.ctx.Err() } log.Printf("Connecting LND HTLC interceptor.") - cancellableCtx, cancel := context.WithCancel(context.Background()) - interceptorClient, err := i.client.routerClient.HtlcInterceptor(cancellableCtx) + interceptorClient, err := i.client.routerClient.HtlcInterceptor(i.ctx) if err != nil { log.Printf("routerClient.HtlcInterceptor(): %v", err) - cancel() <-time.After(time.Second) continue } for { - if i.stopRequested { - cancel() - return nil + if i.ctx.Err() != nil { + return i.ctx.Err() } + + if !inited { + inited = true + i.initWg.Done() + } + request, err := interceptorClient.Recv() if err != nil { // If it is just the error result of the context cancellation // the we exit silently. status, ok := status.FromError(err) if ok && status.Code() == codes.Canceled { + log.Printf("Got code canceled. Break.") break } + // Otherwise it an unexpected error, we fail the test. log.Printf("unexpected error in interceptor.Recv() %v", err) - cancel() break } + fmt.Printf("htlc: %v\nchanID: %v\nincoming amount: %v\noutgoing amount: %v\nincomin expiry: %v\noutgoing expiry: %v\npaymentHash: %x\nonionBlob: %x\n\n", request.IncomingCircuitKey.HtlcId, request.IncomingCircuitKey.ChanId, @@ -131,7 +145,6 @@ func (i *LndHtlcInterceptor) intercept() error { }() } - cancel() <-time.After(time.Second) } } diff --git a/main.go b/main.go index e447194..208a6a2 100644 --- a/main.go +++ b/main.go @@ -4,7 +4,9 @@ import ( "fmt" "log" "os" + "os/signal" "sync" + "syscall" "github.com/btcsuite/btcd/btcec/v2" ) @@ -86,6 +88,15 @@ func main() { wg.Done() }() + c := make(chan os.Signal, 1) + signal.Notify(c, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT) + go func() { + sig := <-c + log.Printf("Received stop signal %v. Stopping.", sig) + s.Stop() + interceptor.Stop() + }() + wg.Wait() log.Printf("lspd exited") } From 5a419fbff2fef6f266d9f94d26652c0942ab0658 Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Fri, 23 Dec 2022 19:03:44 +0100 Subject: [PATCH 127/214] update readme with new plugin wrapper --- README.md | 47 +++++++++++++++++++++++++++++------------------ 1 file changed, 29 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 03aed8c..f4712bd 100644 --- a/README.md +++ b/README.md @@ -14,32 +14,29 @@ This is a simple example of an lspd that works with an [lnd](https://github.com/ ### Running lspd on LND 1. Run LND with the following options set: - - `--protocol.zero-conf`: for being able to open zero conf channels - - `--protocol.option-scid-alias`: required for zero conf channels + - `--protocol.zero-conf`: for being able to open zero conf channels + - `--protocol.option-scid-alias`: required for zero conf channels - `--requireinterceptor`: to make sure all htlcs are intercepted by lspd - - `--bitcoin.chanreservescript="0"` to allow the client to have zero reserve on their side + - `--bitcoin.chanreservescript="0"` to allow the client to have zero reserve on their side 1. Run lspd ### Running lspd on CLN -lspd on core lightning is run as a plugin. In order to load the environment veriables in the plugin runtime, they either have to be set on the machine, or the plugin should be started with a shell script: +In order to run lspd on top of CLN, you need to run the lspd process and run cln with the provided cln plugin. + +The cln plugin (go build -o lspd_plugin cln_plugin/cmd) is best started with a bash script to pass environment variables (note this LISTEN_ADDRESS is the listen address for communication between lspd and the plugin, this is not the listen address mentioned in the 'final step') ```bash #!/bin/bash -export TOKEN= -export LSPD_PRIVATE_KEY= -export DATABASE_URL= -export RUN_CLN=true - -# Etc. for all env variables in sample.env - -/path/to/lspd +export LISTEN_ADDRESS= +/path/to/lspd_plugin ``` -Run cln with the following options set: -- `--plugin=/path/to/shell/script.sh`: to use lspd as plugin -- `--max-concurrent-htlcs=30`: In order to use zero reserve channels on the client side, (local max_accepted_htlcs + remote max_accepted_htlcs + 2) * dust limit must be lower than the channel capacity. Reduce max-concurrent-htlcs or increase channel capacity accordingly. -- `--dev-allowdustreserve=true`: In order to allow zero reserve on the client side, you'll need to enable developer mode on cln (`./configure --enable-developer`) +1. Run cln with the following options set: + - `--plugin=/path/to/shell/script.sh`: to use lspd as plugin + - `--max-concurrent-htlcs=30`: In order to use zero reserve channels on the client side, (local max_accepted_htlcs + remote max_accepted_htlcs + 2) * dust limit must be lower than the channel capacity. Reduce max-concurrent-htlcs or increase channel capacity accordingly. + - `--dev-allowdustreserve=true`: In order to allow zero reserve on the client side, you'll need to enable developer mode on cln (`./configure --enable-developer`) +1. Run lspd ### Final step 1. Share with Breez the TOKEN and the LISTEN_ADDRESS you've defined (send to contact@breez.technology) @@ -64,15 +61,17 @@ In order to run the integration tests, you need: - lnd v0.15.3 breez client version https://github.com/breez/lnd/commit/e1570b327b5de52d03817ad516d0bdfa71797c64 - bitcoind (tested with v23.0) - bitcoin-cli (tested with v23.0) -- build of lspd +- build of lspd (go build .) +- build of lspd cln plugin (go build -o lspd_plugin cln_plugin/cmd) To run the integration tests, run the following command from the lspd root directory (replacing the appropriate paths). ``` -go test -v ./itest \ +go test -timeout 20m -v ./itest \ --lightningdexec /full/path/to/lightningd \ --lndexec /full/path/to/lnd \ --lndmobileexec /full/path/to/lnd \ + --clnpluginexec /full/path/to/lspd_plugin \ --lspdexec /full/path/to/lspd \ --lspdmigrationsdir /full/path/to/lspd/postgresql/migrations ``` @@ -81,6 +80,7 @@ go test -v ./itest \ - Required: `--lndexec` Full path to LSP LND executable. Defaults to `lnd` in `$PATH`. - Required: `--lndmobileexec` Full path to Breez mobile client LND executable. No default. - Required: `--lspdexec` Full path to `lspd` executable to test. Defaults to `lspd` in `$PATH`. +- Required: `--clnpluginexec` Full path to the lspd cln plugin executable. No default. - Required: `--lspdmigrationsdir` Path to directory containing postgres migrations for lspd. (Should be `./postgresql/migrations`) - Recommended: `--bitcoindexec` Full path to `bitcoind`. Defaults to `bitcoind` in `$PATH`. - Recommended: `--bitcoincliexec` Full path to `bitcoin-cli`. Defaults to `bitcoin-cli` in `$PATH`. @@ -88,3 +88,14 @@ go test -v ./itest \ - Optional: `--preservelogs` persists only the logs in the testing directory. - Optional: `--preservestate` preserves all artifacts from the lightning nodes, miners, postgres container and startup scripts. - Optional: `--dumplogs` dumps all logs to the console after a test is complete. + +Unfortunately the tests cannot be cancelled with CTRL+C without having to clean +up some artefacts. Here's where to look: +- lspd process +- lightningd process +- lnd process +- bitcoind process +- docker container for postgres with default name + +It may be a good idea to clean your testdir every once in a while if you're +using the `preservelogs` or `preservestate` flags. \ No newline at end of file From 3c558ab5adb3b6710e9a222f71bc03f7e11697af Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Sat, 24 Dec 2022 23:20:49 +0100 Subject: [PATCH 128/214] cleanup unused stuff in cln plugin + handle done --- cln_plugin/cln_plugin.go | 7 ------- cln_plugin/server.go | 33 ++++++++++++++------------------- 2 files changed, 14 insertions(+), 26 deletions(-) diff --git a/cln_plugin/cln_plugin.go b/cln_plugin/cln_plugin.go index 3a8442f..e104f75 100644 --- a/cln_plugin/cln_plugin.go +++ b/cln_plugin/cln_plugin.go @@ -54,13 +54,6 @@ func (c *ClnPlugin) onInit(plugin *glightning.Plugin, options map[string]glightn log.Printf("ERROR Server stopped with error: %v", err) } }() - - //lightning server - clientcln := glightning.NewLightning() - clientcln.SetTimeout(60) - clientcln.StartUp(config.RpcFile, config.LightningDir) - - log.Printf("successfull clientcln.StartUp") } func (c *ClnPlugin) onHtlcAccepted(event *glightning.HtlcAcceptedEvent) (*glightning.HtlcAcceptedResponse, error) { diff --git a/cln_plugin/server.go b/cln_plugin/server.go index b677b96..7bf91ea 100644 --- a/cln_plugin/server.go +++ b/cln_plugin/server.go @@ -26,7 +26,6 @@ type server struct { subscription *subscription newSubscriber chan struct{} done chan struct{} - err chan error correlations map[uint64]chan *HtlcResolution index uint64 } @@ -35,8 +34,6 @@ func NewServer(listenAddress string) *server { return &server{ listenAddress: listenAddress, newSubscriber: make(chan struct{}, 1), - done: make(chan struct{}), - err: make(chan error, 1), correlations: make(map[uint64]chan *HtlcResolution), index: 0, } @@ -56,6 +53,7 @@ func (s *server) Start() error { return err } + s.done = make(chan struct{}) s.grpcServer = grpc.NewServer() s.startMtx.Unlock() RegisterClnPluginServer(s.grpcServer, s) @@ -110,18 +108,13 @@ func (s *server) HtlcStream(stream ClnPlugin_HtlcStreamServer) error { s.startMtx.Unlock() }() - for { - select { - case <-s.done: - log.Printf("HTLC server signalled done. Return EOF.") - return io.EOF - case err := <-s.err: - log.Printf("HTLC server signalled error: %v", err) - return err - case <-sb.done: - log.Printf("HTLC stream signalled done. Return EOF.") - return io.EOF - } + select { + case <-s.done: + log.Printf("HTLC server signalled done. Return EOF.") + return io.EOF + case <-sb.done: + log.Printf("HTLC stream signalled done. Return EOF.") + return io.EOF } } @@ -189,7 +182,12 @@ func (s *server) recv() *HtlcResolution { // TODO: close the subscription?? log.Printf("Recv() errored, waiting %v: %v", receiveWaitDelay, err) - <-time.After(receiveWaitDelay) + select { + case <-s.done: + log.Printf("Done signalled, stopping receive.") + return s.defaultResolution() + case <-time.After(receiveWaitDelay): + } } } @@ -199,9 +197,6 @@ func (s *server) listenHtlcResponses() { case <-s.done: log.Printf("listenHtlcResponses received done. Stopping listening.") return - case err := <-s.err: - log.Printf("listenHtlcResponses received error %v. Stopping listening.", err) - return default: response := s.recv() s.corrMtx.Lock() From 2bea61d8e7980a504271b8f75bfa6d7fd86beca0 Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Thu, 29 Dec 2022 23:54:16 +0100 Subject: [PATCH 129/214] use internal cln id as correlation id --- cln_interceptor.go | 100 ++- cln_plugin/cln_messages.go | 117 +++ cln_plugin/cln_plugin.go | 538 +++++++++++-- cln_plugin/cln_plugin.pb.go | 674 ---------------- cln_plugin/cmd/main.go | 23 +- cln_plugin/genproto.sh | 2 +- cln_plugin/proto/cln_plugin.pb.go | 787 +++++++++++++++++++ cln_plugin/proto/cln_plugin.proto | 50 +- cln_plugin/{ => proto}/cln_plugin_grpc.pb.go | 4 +- cln_plugin/sample.env | 1 - cln_plugin/server.go | 382 ++++++--- itest/cln_lspd_node.go | 16 +- 12 files changed, 1746 insertions(+), 948 deletions(-) create mode 100644 cln_plugin/cln_messages.go delete mode 100644 cln_plugin/cln_plugin.pb.go create mode 100644 cln_plugin/proto/cln_plugin.pb.go rename cln_plugin/{ => proto}/cln_plugin_grpc.pb.go (98%) delete mode 100644 cln_plugin/sample.env diff --git a/cln_interceptor.go b/cln_interceptor.go index ab082fd..194075c 100644 --- a/cln_interceptor.go +++ b/cln_interceptor.go @@ -3,6 +3,7 @@ package main import ( "bytes" "context" + "encoding/hex" "fmt" "io" "log" @@ -10,7 +11,7 @@ import ( "sync" "time" - "github.com/breez/lspd/cln_plugin" + "github.com/breez/lspd/cln_plugin/proto" sphinx "github.com/lightningnetwork/lightning-onion" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/record" @@ -24,7 +25,7 @@ import ( type ClnHtlcInterceptor struct { pluginAddress string client *ClnClient - pluginClient cln_plugin.ClnPluginClient + pluginClient proto.ClnPluginClient initWg sync.WaitGroup doneWg sync.WaitGroup ctx context.Context @@ -51,7 +52,7 @@ func (i *ClnHtlcInterceptor) Start() error { return err } - i.pluginClient = cln_plugin.NewClnPluginClient(conn) + i.pluginClient = proto.NewClnPluginClient(conn) i.ctx = ctx i.cancel = cancel return i.intercept() @@ -111,7 +112,7 @@ func (i *ClnHtlcInterceptor) intercept() error { request.Htlc, request.Onion.ShortChannelId, request.Htlc.AmountMsat, //with fees - request.Onion.ForwardAmountMsat, + request.Onion.ForwardMsat, request.Htlc.CltvExpiryRelative, request.Htlc.CltvExpiry, request.Htlc.PaymentHash, @@ -120,28 +121,25 @@ func (i *ClnHtlcInterceptor) intercept() error { i.doneWg.Add(1) go func() { - interceptResult := intercept(request.Htlc.PaymentHash, request.Onion.ForwardAmountMsat, request.Htlc.CltvExpiry) + paymentHash, err := hex.DecodeString(request.Htlc.PaymentHash) + if err != nil { + interceptorClient.Send(i.defaultResolution(request)) + i.doneWg.Done() + } + interceptResult := intercept(paymentHash, request.Onion.ForwardMsat, request.Htlc.CltvExpiry) switch interceptResult.action { case INTERCEPT_RESUME_WITH_ONION: interceptorClient.Send(i.resumeWithOnion(request, interceptResult)) case INTERCEPT_FAIL_HTLC_WITH_CODE: - interceptorClient.Send(&cln_plugin.HtlcResolution{ - Correlationid: request.Correlationid, - Outcome: &cln_plugin.HtlcResolution_Fail{ - Fail: &cln_plugin.HtlcFail{ - FailureMessage: i.mapFailureCode(interceptResult.failureCode), - }, - }, - }) + interceptorClient.Send( + i.failWithCode(request, interceptResult.failureCode), + ) case INTERCEPT_RESUME: fallthrough default: - interceptorClient.Send(&cln_plugin.HtlcResolution{ - Correlationid: request.Correlationid, - Outcome: &cln_plugin.HtlcResolution_Continue{ - Continue: &cln_plugin.HtlcContinue{}, - }, - }) + interceptorClient.Send( + i.defaultResolution(request), + ) } i.doneWg.Done() @@ -163,29 +161,51 @@ func (i *ClnHtlcInterceptor) WaitStarted() LightningClient { return i.client } -func (i *ClnHtlcInterceptor) resumeWithOnion(request *cln_plugin.HtlcAccepted, interceptResult interceptResult) *cln_plugin.HtlcResolution { +func (i *ClnHtlcInterceptor) resumeWithOnion(request *proto.HtlcAccepted, interceptResult interceptResult) *proto.HtlcResolution { //decoding and encoding onion with alias in type 6 record. - newPayload, err := encodePayloadWithNextHop(request.Onion.Payload, interceptResult.channelId) + payload, err := hex.DecodeString(request.Onion.Payload) + if err != nil { + log.Printf("resumeWithOnion: hex.DecodeString(%v) error: %v", request.Onion.Payload, err) + return i.failWithCode(request, FAILURE_TEMPORARY_CHANNEL_FAILURE) + } + newPayload, err := encodePayloadWithNextHop(payload, interceptResult.channelId) if err != nil { log.Printf("encodePayloadWithNextHop error: %v", err) - return &cln_plugin.HtlcResolution{ - Correlationid: request.Correlationid, - Outcome: &cln_plugin.HtlcResolution_Fail{ - Fail: &cln_plugin.HtlcFail{ - FailureMessage: i.mapFailureCode(FAILURE_TEMPORARY_CHANNEL_FAILURE), - }, - }, - } + return i.failWithCode(request, FAILURE_TEMPORARY_CHANNEL_FAILURE) } - chanId := lnwire.NewChanIDFromOutPoint(interceptResult.channelPoint) + newPayloadStr := hex.EncodeToString(newPayload) + + chanId := lnwire.NewChanIDFromOutPoint(interceptResult.channelPoint).String() log.Printf("forwarding htlc to the destination node and a new private channel was opened") - return &cln_plugin.HtlcResolution{ + return &proto.HtlcResolution{ Correlationid: request.Correlationid, - Outcome: &cln_plugin.HtlcResolution_ContinueWith{ - ContinueWith: &cln_plugin.HtlcContinueWith{ - ChannelId: chanId[:], - Payload: newPayload, + Outcome: &proto.HtlcResolution_Continue{ + Continue: &proto.HtlcContinue{ + ForwardTo: &chanId, + Payload: &newPayloadStr, + }, + }, + } +} + +func (i *ClnHtlcInterceptor) defaultResolution(request *proto.HtlcAccepted) *proto.HtlcResolution { + return &proto.HtlcResolution{ + Correlationid: request.Correlationid, + Outcome: &proto.HtlcResolution_Continue{ + Continue: &proto.HtlcContinue{}, + }, + } +} + +func (i *ClnHtlcInterceptor) failWithCode(request *proto.HtlcAccepted, code interceptFailureCode) *proto.HtlcResolution { + return &proto.HtlcResolution{ + Correlationid: request.Correlationid, + Outcome: &proto.HtlcResolution_Fail{ + Fail: &proto.HtlcFail{ + Failure: &proto.HtlcFail_FailureMessage{ + FailureMessage: i.mapFailureCode(code), + }, }, }, } @@ -237,16 +257,16 @@ func encodePayloadWithNextHop(payload []byte, channelId uint64) ([]byte, error) return newPayloadBuf.Bytes(), nil } -func (i *ClnHtlcInterceptor) mapFailureCode(original interceptFailureCode) []byte { +func (i *ClnHtlcInterceptor) mapFailureCode(original interceptFailureCode) string { switch original { case FAILURE_TEMPORARY_CHANNEL_FAILURE: - return []byte{0x10, 0x07} + return "1007" case FAILURE_TEMPORARY_NODE_FAILURE: - return []byte{0x20, 0x02} + return "2002" case FAILURE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS: - return []byte{0x40, 0x0F} + return "400F" default: log.Printf("Unknown failure code %v, default to temporary channel failure.", original) - return []byte{0x10, 0x07} // temporary channel failure + return "1007" // temporary channel failure } } diff --git a/cln_plugin/cln_messages.go b/cln_plugin/cln_messages.go new file mode 100644 index 0000000..aed6a5e --- /dev/null +++ b/cln_plugin/cln_messages.go @@ -0,0 +1,117 @@ +package cln_plugin + +import ( + "encoding/json" +) + +type Request struct { + Id json.RawMessage `json:"id,omitempty"` + Method string `json:"method"` + JsonRpc string `json:"jsonrpc"` + Params json.RawMessage `json:"params,omitempty"` +} + +type Response struct { + Id json.RawMessage `json:"id"` + JsonRpc string `json:"jsonrpc"` + Result Result `json:"result,omitempty"` + Error *RpcError `json:"error,omitempty"` +} + +type Result interface{} + +type RpcError struct { + Code int `json:"code"` + Message string `json:"message"` + Data json.RawMessage `json:"data,omitempty"` +} + +type Manifest struct { + Options []Option `json:"options"` + RpcMethods []*RpcMethod `json:"rpcmethods"` + Dynamic bool `json:"dynamic"` + Subscriptions []string `json:"subscriptions,omitempty"` + Hooks []Hook `json:"hooks,omitempty"` + FeatureBits *FeatureBits `json:"featurebits,omitempty"` + NonNumericIds bool `json:"nonnumericids"` +} + +type Option struct { + Name string `json:"name"` + Type string `json:"type"` + Description string `json:"description"` + Default *string `json:"default,omitempty"` + Multi *bool `json:"multi,omitempty"` + Deprecated *bool `json:"deprecated,omitempty"` +} + +type RpcMethod struct { + Name string `json:"name"` + Usage string `json:"usage"` + Description string `json:"description"` + LongDescription *string `json:"long_description,omitempty"` + Deprecated *bool `json:"deprecated,omitempty"` +} + +type Hook struct { + Name string `json:"name"` + Before []string `json:"before,omitempty"` +} + +type FeatureBits struct { + Node *string `json:"node,omitempty"` + Init *string `json:"init,omitempty"` + Channel *string `json:"channel,omitempty"` + Invoice *string `json:"invoice,omitempty"` +} + +type InitMessage struct { + Options map[string]interface{} `json:"options,omitempty"` + Configuration *InitConfiguration `json:"configuration,omitempty"` +} + +type InitConfiguration struct { + LightningDir string `json:"lightning-dir"` + RpcFile string `json:"rpc-file"` + Startup bool `json:"startup"` + Network string `json:"network"` + FeatureSet *FeatureBits `json:"feature_set"` + Proxy *Proxy `json:"proxy"` + TorV3Enabled bool `json:"torv3-enabled"` + AlwaysUseProxy bool `json:"always_use_proxy"` +} + +type Proxy struct { + Type string `json:"type"` + Address string `json:"address"` + Port int `json:"port"` +} + +type HtlcAccepted struct { + Onion *Onion `json:"onion"` + Htlc *Htlc `json:"htlc"` + ForwardTo string `json:"forward_to"` +} + +type Onion struct { + Payload string `json:"payload"` + ShortChannelId string `json:"short_channel_id"` + ForwardMsat uint64 `json:"forward_msat"` + OutgoingCltvValue uint32 `json:"outgoing_cltv_value"` + SharedSecret string `json:"shared_secret"` + NextOnion string `json:"next_onion"` +} + +type Htlc struct { + ShortChannelId string `json:"short_channel_id"` + Id uint64 `json:"id"` + AmountMsat uint64 `json:"amount_msat"` + CltvExpiry uint32 `json:"cltv_expiry"` + CltvExpiryRelative uint32 `json:"cltv_expiry_relative"` + PaymentHash string `json:"payment_hash"` +} + +type LogNotification struct { + Level string `json:"level"` + Message string `json:"message"` +} diff --git a/cln_plugin/cln_plugin.go b/cln_plugin/cln_plugin.go index e104f75..987fba6 100644 --- a/cln_plugin/cln_plugin.go +++ b/cln_plugin/cln_plugin.go @@ -1,110 +1,488 @@ package cln_plugin import ( - "encoding/hex" + "bufio" + "encoding/json" + "fmt" + "io" "log" "os" + "strings" + "sync" + "time" +) - "github.com/breez/lspd/basetypes" - "github.com/niftynei/glightning/glightning" +const ( + SubscriberTimeoutOption = "lsp.subscribertimeout" + ListenAddressOption = "lsp.listen" +) + +var ( + DefaultSubscriberTimeout = "1m" +) + +const ( + MaxIntakeBuffer = 500 * 1024 * 1023 +) + +const ( + SpecVersion = "2.0" + ParseError = -32700 + InvalidRequest = -32600 + MethodNotFound = -32601 + InvalidParams = -32603 + InternalErr = -32603 +) + +var ( + TwoNewLines = []byte("\n\n") ) type ClnPlugin struct { - server *server - plugin *glightning.Plugin + done chan struct{} + server *server + in *os.File + out *bufio.Writer + writeMtx sync.Mutex } -func NewClnPlugin(server *server) *ClnPlugin { +func NewClnPlugin(in, out *os.File) *ClnPlugin { c := &ClnPlugin{ - server: server, + done: make(chan struct{}), + in: in, + out: bufio.NewWriter(out), } - c.plugin = glightning.NewPlugin(c.onInit) - c.plugin.RegisterHooks(&glightning.Hooks{ - HtlcAccepted: c.onHtlcAccepted, - }) - return c } -func (c *ClnPlugin) Start() error { - err := c.plugin.Start(os.Stdin, os.Stdout) - if err != nil { - log.Printf("Plugin error: %v", err) - return err - } - - return nil +// Starts the cln plugin. +// NOTE: The grpc server is started in the handleInit function. +func (c *ClnPlugin) Start() { + c.setupLogging() + go c.listen() + <-c.done } -func (c *ClnPlugin) Stop() { - c.plugin.Stop() - c.server.Stop() -} +func (c *ClnPlugin) setupLogging() { + in, out := io.Pipe() + go func(in io.Reader) { + // everytime we get a new message, log it thru c-lightning + scanner := bufio.NewScanner(in) + for { + select { + case <-c.done: + return + default: + if !scanner.Scan() { + if err := scanner.Err(); err != nil { + log.Fatalf( + "can't print out to std err, killing: %v", + err, + ) + } + } -func (c *ClnPlugin) onInit(plugin *glightning.Plugin, options map[string]glightning.Option, config *glightning.Config) { - log.Printf("successfully init'd! %v\n", config.RpcFile) - - log.Printf("Starting htlc grpc server.") - go func() { - err := c.server.Start() - if err == nil { - log.Printf("WARNING server stopped.") - } else { - log.Printf("ERROR Server stopped with error: %v", err) + for _, line := range strings.Split(scanner.Text(), "\n") { + c.log("info", line) + } + } } - }() + + }(in) + log.SetFlags(log.Ltime | log.Lshortfile) + log.SetOutput(out) } -func (c *ClnPlugin) onHtlcAccepted(event *glightning.HtlcAcceptedEvent) (*glightning.HtlcAcceptedResponse, error) { - payload, err := hex.DecodeString(event.Onion.Payload) - if err != nil { - log.Printf("ERROR failed to decode payload %s: %v", event.Onion.Payload, err) - return nil, err +// Stops the cln plugin. Drops any remaining work immediately. +// Pending htlcs will be replayed when cln starts again. +func (c *ClnPlugin) Stop() { + close(c.done) + + s := c.server + if s != nil { + s.Stop() } - scid, err := basetypes.NewShortChannelIDFromString(event.Onion.ShortChannelId) - if err != nil { - log.Printf("ERROR failed to decode short channel id %s: %v", event.Onion.ShortChannelId, err) - return nil, err +} + +// listens stdout for requests from cln and sends the requests to the +// appropriate handler in fifo order. +func (c *ClnPlugin) listen() error { + scanner := bufio.NewScanner(c.in) + buf := make([]byte, 1024) + scanner.Buffer(buf, MaxIntakeBuffer) + + // cln messages are split by a double newline. + scanner.Split(scanDoubleNewline) + for { + select { + case <-c.done: + return nil + default: + if !scanner.Scan() { + if err := scanner.Err(); err != nil { + log.Fatal(err) + return err + } + + return nil + } + + msg := scanner.Bytes() + // TODO: Pipe logs to the proper place. + log.Println(string(msg)) + // pass down a copy so things stay sane + msg_buf := make([]byte, len(msg)) + copy(msg_buf, msg) + + // NOTE: processMsg is synchronous, so it should only do quick work. + c.processMsg(msg_buf) + } } - ph, err := hex.DecodeString(event.Htlc.PaymentHash) - if err != nil { - log.Printf("ERROR failed to decode payment hash %s: %v", event.Onion.ShortChannelId, err) - return nil, err +} + +// processes a single message from cln. Sends the message to the appropriate +// handler. +func (c *ClnPlugin) processMsg(msg []byte) { + if len(msg) == 0 { + c.sendError(nil, InvalidRequest, "Invalid Request") + return } - resp := c.server.Send(&HtlcAccepted{ - Onion: &Onion{ - Payload: payload, - ShortChannelId: uint64(*scid), - ForwardAmountMsat: event.Onion.ForwardAmount, - }, - Htlc: &HtlcOffer{ - AmountMsat: event.Htlc.AmountMilliSatoshi, - CltvExpiryRelative: uint32(event.Htlc.CltvExpiryRelative), - CltvExpiry: uint32(event.Htlc.CltvExpiry), - PaymentHash: ph, + // Right now we don't handle arrays of requests... + if msg[0] == '[' { + var requests []*Request + err := json.Unmarshal(msg, &requests) + if err != nil { + c.sendError( + nil, + ParseError, + fmt.Sprintf("Parse error:%s [%s]", err.Error(), msg), + ) + return + } + + for _, request := range requests { + c.processRequest(request) + } + + return + } + + // Parse the received buffer into a request object. + var request Request + err := json.Unmarshal(msg, &request) + if err != nil { + log.Printf("failed to unmarshal request: %v", err) + c.sendError( + nil, + ParseError, + fmt.Sprintf("Parse error:%s [%s]", err.Error(), msg), + ) + return + } + + c.processRequest(&request) +} + +func (c *ClnPlugin) processRequest(request *Request) { + // Make sure the spec version is expected. + if request.JsonRpc != SpecVersion { + c.sendError( + request.Id, + InvalidRequest, + fmt.Sprintf( + `Invalid jsonrpc, expected '%s' got '%s'`, + SpecVersion, + request.JsonRpc, + ), + ) + return + } + + // Send the message to the appropriate handler. + switch request.Method { + case "getmanifest": + c.handleGetManifest(request) + case "init": + c.handleInit(request) + case "shutdown": + c.handleShutdown(request) + case "htlc_accepted": + c.handleHtlcAccepted(request) + default: + c.sendError( + request.Id, + MethodNotFound, + fmt.Sprintf("Method '%s' not found", request.Method), + ) + } +} + +// Returns this plugin's manifest to cln. +func (c *ClnPlugin) handleGetManifest(request *Request) { + c.sendToCln(&Response{ + Id: request.Id, + JsonRpc: SpecVersion, + Result: &Manifest{ + Options: []Option{ + { + Name: ListenAddressOption, + Type: "string", + Description: "listen address for the htlc_accepted lsp " + + "grpc server", + }, + { + Name: SubscriberTimeoutOption, + Type: "string", + Description: "htlc timeout duration when there is no " + + "subscriber to the grpc server. golang duration " + + "string.", + Default: &DefaultSubscriberTimeout, + }, + }, + RpcMethods: []*RpcMethod{}, + Dynamic: true, + Hooks: []Hook{ + { + Name: "htlc_accepted", + }, + }, + NonNumericIds: true, + Subscriptions: []string{ + "shutdown", + }, }, }) - - _, ok := resp.Outcome.(*HtlcResolution_Continue) - if ok { - return event.Continue(), nil - } - - cont, ok := resp.Outcome.(*HtlcResolution_ContinueWith) - if ok { - chanId := hex.EncodeToString(cont.ContinueWith.ChannelId) - pl := hex.EncodeToString(cont.ContinueWith.Payload) - return event.ContinueWith(chanId, pl), nil - } - - fail, ok := resp.Outcome.(*HtlcResolution_Fail) - if ok { - fm := hex.EncodeToString(fail.Fail.FailureMessage) - return event.Fail(fm), err - } - - log.Printf("Unexpected htlc resolution type %T: %+v", resp.Outcome, resp.Outcome) - return event.Fail("1007"), nil // temporary channel failure +} + +// Handles plugin initialization. Parses startup options and starts the grpc +// server. +func (c *ClnPlugin) handleInit(request *Request) { + // Deserialize the init message. + var initMsg InitMessage + err := json.Unmarshal(request.Params, &initMsg) + if err != nil { + c.sendError( + request.Id, + ParseError, + fmt.Sprintf( + "Error parsing init params:%s [%s]", + err.Error(), + request.Params, + ), + ) + return + } + + // Get the listen address option. + l, ok := initMsg.Options[ListenAddressOption] + if !ok { + c.sendError( + request.Id, + InvalidParams, + fmt.Sprintf("Missing option '%s'", ListenAddressOption), + ) + return + } + + addr, ok := l.(string) + if !ok || addr == "" { + c.sendError( + request.Id, + InvalidParams, + fmt.Sprintf( + "Invalid value '%v' for option '%s'", + l, + ListenAddressOption, + ), + ) + return + } + + // Get the subscriber timeout option. + t, ok := initMsg.Options[SubscriberTimeoutOption] + if !ok { + c.sendError( + request.Id, + InvalidParams, + fmt.Sprintf("Missing option '%s'", SubscriberTimeoutOption), + ) + return + } + + s, ok := t.(string) + if !ok || s == "" { + c.sendError( + request.Id, + InvalidParams, + fmt.Sprintf( + "Invalid value '%v' for option '%s'", + t, + SubscriberTimeoutOption, + ), + ) + return + } + + subscriberTimeout, err := time.ParseDuration(s) + if err != nil { + c.sendError( + request.Id, + InvalidParams, + fmt.Sprintf( + "Invalid value '%v' for option '%s'", + s, + SubscriberTimeoutOption, + ), + ) + return + } + + // Start the grpc server. + c.server = NewServer(addr, subscriberTimeout) + go c.server.Start() + err = c.server.WaitStarted() + if err != nil { + c.sendError( + request.Id, + InternalErr, + fmt.Sprintf("Failed to start server: %s", err.Error()), + ) + return + } + + // Listen for responses from the grpc server. + go c.listenServer() + + // Let cln know the plugin is initialized. + c.sendToCln(&Response{ + Id: request.Id, + JsonRpc: SpecVersion, + }) +} + +// Listens to responses to htlc_accepted requests from the grpc server. +func (c *ClnPlugin) listenServer() { + for { + select { + case <-c.done: + return + default: + id, result := c.server.Receive() + + // The server may return nil if it is stopped. + if result == nil { + continue + } + + serid, _ := json.Marshal(&id) + c.sendToCln(&Response{ + Id: serid, + JsonRpc: SpecVersion, + Result: result, + }) + } + } +} + +// Handles the shutdown message. Stops any work immediately. +func (c *ClnPlugin) handleShutdown(request *Request) { + c.Stop() +} + +// Sends a htlc_accepted message to the grpc server. +func (c *ClnPlugin) handleHtlcAccepted(request *Request) { + var htlc HtlcAccepted + err := json.Unmarshal(request.Params, &htlc) + if err != nil { + c.sendError( + request.Id, + ParseError, + fmt.Sprintf( + "Error parsing htlc_accepted params:%s [%s]", + err.Error(), + request.Params, + ), + ) + return + } + + c.server.Send(c.idToString(request.Id), &htlc) +} + +// Sends an error to cln. +func (c *ClnPlugin) sendError(id json.RawMessage, code int, message string) { + resp := &Response{ + JsonRpc: SpecVersion, + Error: &RpcError{ + Code: code, + Message: message, + }, + } + + if len(id) > 0 { + resp.Id = id + } + + c.sendToCln(resp) +} + +// converts a raw cln id to string. The CLN id can either be an integer or a +// string. if it's a string, the quotes are removed. +func (c *ClnPlugin) idToString(id json.RawMessage) string { + if len(id) == 0 { + return "" + } + + str := string(id) + str = strings.TrimSpace(str) + str = strings.Trim(str, "\"") + str = strings.Trim(str, "'") + return str +} + +// Sends a message to cln. +func (c *ClnPlugin) sendToCln(msg interface{}) { + c.writeMtx.Lock() + defer c.writeMtx.Unlock() + + // TODO: log + data, err := json.Marshal(msg) + if err != nil { + log.Println(err.Error()) + return + } + + data = append(data, TwoNewLines...) + c.out.Write(data) + c.out.Flush() +} + +func (c *ClnPlugin) log(level string, message string) { + params, _ := json.Marshal(&LogNotification{ + Level: level, + Message: message, + }) + + c.sendToCln(&Request{ + Method: "log", + JsonRpc: SpecVersion, + Params: params, + }) +} + +// Helper method for the bufio scanner to split messages on double newlines. +func scanDoubleNewline( + data []byte, + atEOF bool, +) (advance int, token []byte, err error) { + for i := 0; i < len(data); i++ { + if data[i] == '\n' && (i+1) < len(data) && data[i+1] == '\n' { + return i + 2, data[:i], nil + } + } + // this trashes anything left over in + // the buffer if we're at EOF, with no /n/n present + return 0, nil, nil } diff --git a/cln_plugin/cln_plugin.pb.go b/cln_plugin/cln_plugin.pb.go deleted file mode 100644 index 181a0f0..0000000 --- a/cln_plugin/cln_plugin.pb.go +++ /dev/null @@ -1,674 +0,0 @@ -// Code generated by protoc-gen-go. DO NOT EDIT. -// versions: -// protoc-gen-go v1.28.1 -// protoc v3.21.8 -// source: cln_plugin.proto - -package cln_plugin - -import ( - protoreflect "google.golang.org/protobuf/reflect/protoreflect" - protoimpl "google.golang.org/protobuf/runtime/protoimpl" - reflect "reflect" - sync "sync" -) - -const ( - // Verify that this generated code is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) - // Verify that runtime/protoimpl is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) -) - -type HtlcAccepted struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Correlationid uint64 `protobuf:"varint,1,opt,name=correlationid,proto3" json:"correlationid,omitempty"` - Onion *Onion `protobuf:"bytes,2,opt,name=onion,proto3" json:"onion,omitempty"` - Htlc *HtlcOffer `protobuf:"bytes,3,opt,name=htlc,proto3" json:"htlc,omitempty"` -} - -func (x *HtlcAccepted) Reset() { - *x = HtlcAccepted{} - if protoimpl.UnsafeEnabled { - mi := &file_cln_plugin_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *HtlcAccepted) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*HtlcAccepted) ProtoMessage() {} - -func (x *HtlcAccepted) ProtoReflect() protoreflect.Message { - mi := &file_cln_plugin_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use HtlcAccepted.ProtoReflect.Descriptor instead. -func (*HtlcAccepted) Descriptor() ([]byte, []int) { - return file_cln_plugin_proto_rawDescGZIP(), []int{0} -} - -func (x *HtlcAccepted) GetCorrelationid() uint64 { - if x != nil { - return x.Correlationid - } - return 0 -} - -func (x *HtlcAccepted) GetOnion() *Onion { - if x != nil { - return x.Onion - } - return nil -} - -func (x *HtlcAccepted) GetHtlc() *HtlcOffer { - if x != nil { - return x.Htlc - } - return nil -} - -type Onion struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` - ShortChannelId uint64 `protobuf:"varint,2,opt,name=short_channel_id,json=shortChannelId,proto3" json:"short_channel_id,omitempty"` - ForwardAmountMsat uint64 `protobuf:"varint,3,opt,name=forward_amount_msat,json=forwardAmountMsat,proto3" json:"forward_amount_msat,omitempty"` -} - -func (x *Onion) Reset() { - *x = Onion{} - if protoimpl.UnsafeEnabled { - mi := &file_cln_plugin_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *Onion) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*Onion) ProtoMessage() {} - -func (x *Onion) ProtoReflect() protoreflect.Message { - mi := &file_cln_plugin_proto_msgTypes[1] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use Onion.ProtoReflect.Descriptor instead. -func (*Onion) Descriptor() ([]byte, []int) { - return file_cln_plugin_proto_rawDescGZIP(), []int{1} -} - -func (x *Onion) GetPayload() []byte { - if x != nil { - return x.Payload - } - return nil -} - -func (x *Onion) GetShortChannelId() uint64 { - if x != nil { - return x.ShortChannelId - } - return 0 -} - -func (x *Onion) GetForwardAmountMsat() uint64 { - if x != nil { - return x.ForwardAmountMsat - } - return 0 -} - -type HtlcOffer struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - AmountMsat uint64 `protobuf:"varint,2,opt,name=amount_msat,json=amountMsat,proto3" json:"amount_msat,omitempty"` - CltvExpiryRelative uint32 `protobuf:"varint,3,opt,name=cltv_expiry_relative,json=cltvExpiryRelative,proto3" json:"cltv_expiry_relative,omitempty"` - CltvExpiry uint32 `protobuf:"varint,4,opt,name=cltv_expiry,json=cltvExpiry,proto3" json:"cltv_expiry,omitempty"` - PaymentHash []byte `protobuf:"bytes,5,opt,name=payment_hash,json=paymentHash,proto3" json:"payment_hash,omitempty"` -} - -func (x *HtlcOffer) Reset() { - *x = HtlcOffer{} - if protoimpl.UnsafeEnabled { - mi := &file_cln_plugin_proto_msgTypes[2] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *HtlcOffer) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*HtlcOffer) ProtoMessage() {} - -func (x *HtlcOffer) ProtoReflect() protoreflect.Message { - mi := &file_cln_plugin_proto_msgTypes[2] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use HtlcOffer.ProtoReflect.Descriptor instead. -func (*HtlcOffer) Descriptor() ([]byte, []int) { - return file_cln_plugin_proto_rawDescGZIP(), []int{2} -} - -func (x *HtlcOffer) GetAmountMsat() uint64 { - if x != nil { - return x.AmountMsat - } - return 0 -} - -func (x *HtlcOffer) GetCltvExpiryRelative() uint32 { - if x != nil { - return x.CltvExpiryRelative - } - return 0 -} - -func (x *HtlcOffer) GetCltvExpiry() uint32 { - if x != nil { - return x.CltvExpiry - } - return 0 -} - -func (x *HtlcOffer) GetPaymentHash() []byte { - if x != nil { - return x.PaymentHash - } - return nil -} - -type HtlcResolution struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Correlationid uint64 `protobuf:"varint,1,opt,name=correlationid,proto3" json:"correlationid,omitempty"` - // Types that are assignable to Outcome: - // *HtlcResolution_Fail - // *HtlcResolution_Continue - // *HtlcResolution_ContinueWith - Outcome isHtlcResolution_Outcome `protobuf_oneof:"outcome"` -} - -func (x *HtlcResolution) Reset() { - *x = HtlcResolution{} - if protoimpl.UnsafeEnabled { - mi := &file_cln_plugin_proto_msgTypes[3] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *HtlcResolution) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*HtlcResolution) ProtoMessage() {} - -func (x *HtlcResolution) ProtoReflect() protoreflect.Message { - mi := &file_cln_plugin_proto_msgTypes[3] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use HtlcResolution.ProtoReflect.Descriptor instead. -func (*HtlcResolution) Descriptor() ([]byte, []int) { - return file_cln_plugin_proto_rawDescGZIP(), []int{3} -} - -func (x *HtlcResolution) GetCorrelationid() uint64 { - if x != nil { - return x.Correlationid - } - return 0 -} - -func (m *HtlcResolution) GetOutcome() isHtlcResolution_Outcome { - if m != nil { - return m.Outcome - } - return nil -} - -func (x *HtlcResolution) GetFail() *HtlcFail { - if x, ok := x.GetOutcome().(*HtlcResolution_Fail); ok { - return x.Fail - } - return nil -} - -func (x *HtlcResolution) GetContinue() *HtlcContinue { - if x, ok := x.GetOutcome().(*HtlcResolution_Continue); ok { - return x.Continue - } - return nil -} - -func (x *HtlcResolution) GetContinueWith() *HtlcContinueWith { - if x, ok := x.GetOutcome().(*HtlcResolution_ContinueWith); ok { - return x.ContinueWith - } - return nil -} - -type isHtlcResolution_Outcome interface { - isHtlcResolution_Outcome() -} - -type HtlcResolution_Fail struct { - Fail *HtlcFail `protobuf:"bytes,2,opt,name=fail,proto3,oneof"` -} - -type HtlcResolution_Continue struct { - Continue *HtlcContinue `protobuf:"bytes,3,opt,name=continue,proto3,oneof"` -} - -type HtlcResolution_ContinueWith struct { - ContinueWith *HtlcContinueWith `protobuf:"bytes,4,opt,name=continue_with,json=continueWith,proto3,oneof"` -} - -func (*HtlcResolution_Fail) isHtlcResolution_Outcome() {} - -func (*HtlcResolution_Continue) isHtlcResolution_Outcome() {} - -func (*HtlcResolution_ContinueWith) isHtlcResolution_Outcome() {} - -type HtlcFail struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - FailureMessage []byte `protobuf:"bytes,1,opt,name=failure_message,json=failureMessage,proto3" json:"failure_message,omitempty"` -} - -func (x *HtlcFail) Reset() { - *x = HtlcFail{} - if protoimpl.UnsafeEnabled { - mi := &file_cln_plugin_proto_msgTypes[4] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *HtlcFail) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*HtlcFail) ProtoMessage() {} - -func (x *HtlcFail) ProtoReflect() protoreflect.Message { - mi := &file_cln_plugin_proto_msgTypes[4] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use HtlcFail.ProtoReflect.Descriptor instead. -func (*HtlcFail) Descriptor() ([]byte, []int) { - return file_cln_plugin_proto_rawDescGZIP(), []int{4} -} - -func (x *HtlcFail) GetFailureMessage() []byte { - if x != nil { - return x.FailureMessage - } - return nil -} - -type HtlcContinue struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields -} - -func (x *HtlcContinue) Reset() { - *x = HtlcContinue{} - if protoimpl.UnsafeEnabled { - mi := &file_cln_plugin_proto_msgTypes[5] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *HtlcContinue) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*HtlcContinue) ProtoMessage() {} - -func (x *HtlcContinue) ProtoReflect() protoreflect.Message { - mi := &file_cln_plugin_proto_msgTypes[5] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use HtlcContinue.ProtoReflect.Descriptor instead. -func (*HtlcContinue) Descriptor() ([]byte, []int) { - return file_cln_plugin_proto_rawDescGZIP(), []int{5} -} - -type HtlcContinueWith struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - ChannelId []byte `protobuf:"bytes,1,opt,name=channel_id,json=channelId,proto3" json:"channel_id,omitempty"` - Payload []byte `protobuf:"bytes,2,opt,name=payload,proto3" json:"payload,omitempty"` -} - -func (x *HtlcContinueWith) Reset() { - *x = HtlcContinueWith{} - if protoimpl.UnsafeEnabled { - mi := &file_cln_plugin_proto_msgTypes[6] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *HtlcContinueWith) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*HtlcContinueWith) ProtoMessage() {} - -func (x *HtlcContinueWith) ProtoReflect() protoreflect.Message { - mi := &file_cln_plugin_proto_msgTypes[6] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use HtlcContinueWith.ProtoReflect.Descriptor instead. -func (*HtlcContinueWith) Descriptor() ([]byte, []int) { - return file_cln_plugin_proto_rawDescGZIP(), []int{6} -} - -func (x *HtlcContinueWith) GetChannelId() []byte { - if x != nil { - return x.ChannelId - } - return nil -} - -func (x *HtlcContinueWith) GetPayload() []byte { - if x != nil { - return x.Payload - } - return nil -} - -var File_cln_plugin_proto protoreflect.FileDescriptor - -var file_cln_plugin_proto_rawDesc = []byte{ - 0x0a, 0x10, 0x63, 0x6c, 0x6e, 0x5f, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x22, 0x72, 0x0a, 0x0c, 0x48, 0x74, 0x6c, 0x63, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, - 0x65, 0x64, 0x12, 0x24, 0x0a, 0x0d, 0x63, 0x6f, 0x72, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0d, 0x63, 0x6f, 0x72, 0x72, 0x65, - 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x69, 0x64, 0x12, 0x1c, 0x0a, 0x05, 0x6f, 0x6e, 0x69, 0x6f, - 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x06, 0x2e, 0x4f, 0x6e, 0x69, 0x6f, 0x6e, 0x52, - 0x05, 0x6f, 0x6e, 0x69, 0x6f, 0x6e, 0x12, 0x1e, 0x0a, 0x04, 0x68, 0x74, 0x6c, 0x63, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0a, 0x2e, 0x48, 0x74, 0x6c, 0x63, 0x4f, 0x66, 0x66, 0x65, 0x72, - 0x52, 0x04, 0x68, 0x74, 0x6c, 0x63, 0x22, 0x7b, 0x0a, 0x05, 0x4f, 0x6e, 0x69, 0x6f, 0x6e, 0x12, - 0x18, 0x0a, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, - 0x52, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x28, 0x0a, 0x10, 0x73, 0x68, 0x6f, - 0x72, 0x74, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x04, 0x52, 0x0e, 0x73, 0x68, 0x6f, 0x72, 0x74, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, - 0x6c, 0x49, 0x64, 0x12, 0x2e, 0x0a, 0x13, 0x66, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x5f, 0x61, - 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, - 0x52, 0x11, 0x66, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x4d, - 0x73, 0x61, 0x74, 0x22, 0xa2, 0x01, 0x0a, 0x09, 0x48, 0x74, 0x6c, 0x63, 0x4f, 0x66, 0x66, 0x65, - 0x72, 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x4d, 0x73, - 0x61, 0x74, 0x12, 0x30, 0x0a, 0x14, 0x63, 0x6c, 0x74, 0x76, 0x5f, 0x65, 0x78, 0x70, 0x69, 0x72, - 0x79, 0x5f, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x76, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, - 0x52, 0x12, 0x63, 0x6c, 0x74, 0x76, 0x45, 0x78, 0x70, 0x69, 0x72, 0x79, 0x52, 0x65, 0x6c, 0x61, - 0x74, 0x69, 0x76, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x6c, 0x74, 0x76, 0x5f, 0x65, 0x78, 0x70, - 0x69, 0x72, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x63, 0x6c, 0x74, 0x76, 0x45, - 0x78, 0x70, 0x69, 0x72, 0x79, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, - 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x70, 0x61, 0x79, - 0x6d, 0x65, 0x6e, 0x74, 0x48, 0x61, 0x73, 0x68, 0x22, 0xc9, 0x01, 0x0a, 0x0e, 0x48, 0x74, 0x6c, - 0x63, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x24, 0x0a, 0x0d, 0x63, - 0x6f, 0x72, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x04, 0x52, 0x0d, 0x63, 0x6f, 0x72, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x69, - 0x64, 0x12, 0x1f, 0x0a, 0x04, 0x66, 0x61, 0x69, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x09, 0x2e, 0x48, 0x74, 0x6c, 0x63, 0x46, 0x61, 0x69, 0x6c, 0x48, 0x00, 0x52, 0x04, 0x66, 0x61, - 0x69, 0x6c, 0x12, 0x2b, 0x0a, 0x08, 0x63, 0x6f, 0x6e, 0x74, 0x69, 0x6e, 0x75, 0x65, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x48, 0x74, 0x6c, 0x63, 0x43, 0x6f, 0x6e, 0x74, 0x69, - 0x6e, 0x75, 0x65, 0x48, 0x00, 0x52, 0x08, 0x63, 0x6f, 0x6e, 0x74, 0x69, 0x6e, 0x75, 0x65, 0x12, - 0x38, 0x0a, 0x0d, 0x63, 0x6f, 0x6e, 0x74, 0x69, 0x6e, 0x75, 0x65, 0x5f, 0x77, 0x69, 0x74, 0x68, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x48, 0x74, 0x6c, 0x63, 0x43, 0x6f, 0x6e, - 0x74, 0x69, 0x6e, 0x75, 0x65, 0x57, 0x69, 0x74, 0x68, 0x48, 0x00, 0x52, 0x0c, 0x63, 0x6f, 0x6e, - 0x74, 0x69, 0x6e, 0x75, 0x65, 0x57, 0x69, 0x74, 0x68, 0x42, 0x09, 0x0a, 0x07, 0x6f, 0x75, 0x74, - 0x63, 0x6f, 0x6d, 0x65, 0x22, 0x33, 0x0a, 0x08, 0x48, 0x74, 0x6c, 0x63, 0x46, 0x61, 0x69, 0x6c, - 0x12, 0x27, 0x0a, 0x0f, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x5f, 0x6d, 0x65, 0x73, 0x73, - 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0e, 0x66, 0x61, 0x69, 0x6c, 0x75, - 0x72, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x0e, 0x0a, 0x0c, 0x48, 0x74, 0x6c, - 0x63, 0x43, 0x6f, 0x6e, 0x74, 0x69, 0x6e, 0x75, 0x65, 0x22, 0x4b, 0x0a, 0x10, 0x48, 0x74, 0x6c, - 0x63, 0x43, 0x6f, 0x6e, 0x74, 0x69, 0x6e, 0x75, 0x65, 0x57, 0x69, 0x74, 0x68, 0x12, 0x1d, 0x0a, - 0x0a, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0c, 0x52, 0x09, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, - 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x70, - 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x32, 0x3d, 0x0a, 0x09, 0x43, 0x6c, 0x6e, 0x50, 0x6c, 0x75, - 0x67, 0x69, 0x6e, 0x12, 0x30, 0x0a, 0x0a, 0x48, 0x74, 0x6c, 0x63, 0x53, 0x74, 0x72, 0x65, 0x61, - 0x6d, 0x12, 0x0f, 0x2e, 0x48, 0x74, 0x6c, 0x63, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, - 0x6f, 0x6e, 0x1a, 0x0d, 0x2e, 0x48, 0x74, 0x6c, 0x63, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, - 0x64, 0x28, 0x01, 0x30, 0x01, 0x42, 0x22, 0x5a, 0x20, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, - 0x63, 0x6f, 0x6d, 0x2f, 0x62, 0x72, 0x65, 0x65, 0x7a, 0x2f, 0x6c, 0x73, 0x70, 0x64, 0x2f, 0x63, - 0x6c, 0x6e, 0x5f, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x33, -} - -var ( - file_cln_plugin_proto_rawDescOnce sync.Once - file_cln_plugin_proto_rawDescData = file_cln_plugin_proto_rawDesc -) - -func file_cln_plugin_proto_rawDescGZIP() []byte { - file_cln_plugin_proto_rawDescOnce.Do(func() { - file_cln_plugin_proto_rawDescData = protoimpl.X.CompressGZIP(file_cln_plugin_proto_rawDescData) - }) - return file_cln_plugin_proto_rawDescData -} - -var file_cln_plugin_proto_msgTypes = make([]protoimpl.MessageInfo, 7) -var file_cln_plugin_proto_goTypes = []interface{}{ - (*HtlcAccepted)(nil), // 0: HtlcAccepted - (*Onion)(nil), // 1: Onion - (*HtlcOffer)(nil), // 2: HtlcOffer - (*HtlcResolution)(nil), // 3: HtlcResolution - (*HtlcFail)(nil), // 4: HtlcFail - (*HtlcContinue)(nil), // 5: HtlcContinue - (*HtlcContinueWith)(nil), // 6: HtlcContinueWith -} -var file_cln_plugin_proto_depIdxs = []int32{ - 1, // 0: HtlcAccepted.onion:type_name -> Onion - 2, // 1: HtlcAccepted.htlc:type_name -> HtlcOffer - 4, // 2: HtlcResolution.fail:type_name -> HtlcFail - 5, // 3: HtlcResolution.continue:type_name -> HtlcContinue - 6, // 4: HtlcResolution.continue_with:type_name -> HtlcContinueWith - 3, // 5: ClnPlugin.HtlcStream:input_type -> HtlcResolution - 0, // 6: ClnPlugin.HtlcStream:output_type -> HtlcAccepted - 6, // [6:7] is the sub-list for method output_type - 5, // [5:6] is the sub-list for method input_type - 5, // [5:5] is the sub-list for extension type_name - 5, // [5:5] is the sub-list for extension extendee - 0, // [0:5] is the sub-list for field type_name -} - -func init() { file_cln_plugin_proto_init() } -func file_cln_plugin_proto_init() { - if File_cln_plugin_proto != nil { - return - } - if !protoimpl.UnsafeEnabled { - file_cln_plugin_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*HtlcAccepted); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_cln_plugin_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Onion); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_cln_plugin_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*HtlcOffer); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_cln_plugin_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*HtlcResolution); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_cln_plugin_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*HtlcFail); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_cln_plugin_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*HtlcContinue); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_cln_plugin_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*HtlcContinueWith); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } - file_cln_plugin_proto_msgTypes[3].OneofWrappers = []interface{}{ - (*HtlcResolution_Fail)(nil), - (*HtlcResolution_Continue)(nil), - (*HtlcResolution_ContinueWith)(nil), - } - type x struct{} - out := protoimpl.TypeBuilder{ - File: protoimpl.DescBuilder{ - GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_cln_plugin_proto_rawDesc, - NumEnums: 0, - NumMessages: 7, - NumExtensions: 0, - NumServices: 1, - }, - GoTypes: file_cln_plugin_proto_goTypes, - DependencyIndexes: file_cln_plugin_proto_depIdxs, - MessageInfos: file_cln_plugin_proto_msgTypes, - }.Build() - File_cln_plugin_proto = out.File - file_cln_plugin_proto_rawDesc = nil - file_cln_plugin_proto_goTypes = nil - file_cln_plugin_proto_depIdxs = nil -} diff --git a/cln_plugin/cmd/main.go b/cln_plugin/cmd/main.go index 5a84e46..0fc934d 100644 --- a/cln_plugin/cmd/main.go +++ b/cln_plugin/cmd/main.go @@ -1,31 +1,12 @@ package main import ( - "log" "os" - "os/signal" - "syscall" "github.com/breez/lspd/cln_plugin" ) func main() { - listen := os.Getenv("LISTEN_ADDRESS") - server := cln_plugin.NewServer(listen) - plugin := cln_plugin.NewClnPlugin(server) - - c := make(chan os.Signal, 1) - signal.Notify(c, os.Interrupt, syscall.SIGINT) - go func() { - sig := <-c - log.Printf("Received stop signal %v. Stopping.", sig) - plugin.Stop() - }() - - err := plugin.Start() - if err == nil { - log.Printf("cln plugin stopped.") - } else { - log.Printf("cln plugin stopped with error: %v", err) - } + plugin := cln_plugin.NewClnPlugin(os.Stdin, os.Stdout) + plugin.Start() } diff --git a/cln_plugin/genproto.sh b/cln_plugin/genproto.sh index 226b0fe..e598c4e 100755 --- a/cln_plugin/genproto.sh +++ b/cln_plugin/genproto.sh @@ -2,4 +2,4 @@ SCRIPTDIR=$(dirname $0) PROTO_ROOT=$SCRIPTDIR/proto -protoc --go_out=$SCRIPTDIR --go_opt=paths=source_relative --go-grpc_out=$SCRIPTDIR --go-grpc_opt=paths=source_relative -I=$PROTO_ROOT $PROTO_ROOT/* \ No newline at end of file +protoc --go_out=$PROTO_ROOT --go_opt=paths=source_relative --go-grpc_out=$PROTO_ROOT --go-grpc_opt=paths=source_relative -I=$PROTO_ROOT $PROTO_ROOT/*.proto \ No newline at end of file diff --git a/cln_plugin/proto/cln_plugin.pb.go b/cln_plugin/proto/cln_plugin.pb.go new file mode 100644 index 0000000..72675d6 --- /dev/null +++ b/cln_plugin/proto/cln_plugin.pb.go @@ -0,0 +1,787 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.28.1 +// protoc v3.21.12 +// source: cln_plugin.proto + +package proto + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type HtlcAccepted struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Correlationid string `protobuf:"bytes,1,opt,name=correlationid,proto3" json:"correlationid,omitempty"` + Onion *Onion `protobuf:"bytes,2,opt,name=onion,proto3" json:"onion,omitempty"` + Htlc *Htlc `protobuf:"bytes,3,opt,name=htlc,proto3" json:"htlc,omitempty"` + ForwardTo string `protobuf:"bytes,4,opt,name=forward_to,json=forwardTo,proto3" json:"forward_to,omitempty"` +} + +func (x *HtlcAccepted) Reset() { + *x = HtlcAccepted{} + if protoimpl.UnsafeEnabled { + mi := &file_cln_plugin_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *HtlcAccepted) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HtlcAccepted) ProtoMessage() {} + +func (x *HtlcAccepted) ProtoReflect() protoreflect.Message { + mi := &file_cln_plugin_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HtlcAccepted.ProtoReflect.Descriptor instead. +func (*HtlcAccepted) Descriptor() ([]byte, []int) { + return file_cln_plugin_proto_rawDescGZIP(), []int{0} +} + +func (x *HtlcAccepted) GetCorrelationid() string { + if x != nil { + return x.Correlationid + } + return "" +} + +func (x *HtlcAccepted) GetOnion() *Onion { + if x != nil { + return x.Onion + } + return nil +} + +func (x *HtlcAccepted) GetHtlc() *Htlc { + if x != nil { + return x.Htlc + } + return nil +} + +func (x *HtlcAccepted) GetForwardTo() string { + if x != nil { + return x.ForwardTo + } + return "" +} + +type Onion struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Payload string `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` + ShortChannelId string `protobuf:"bytes,2,opt,name=short_channel_id,json=shortChannelId,proto3" json:"short_channel_id,omitempty"` + ForwardMsat uint64 `protobuf:"varint,3,opt,name=forward_msat,json=forwardMsat,proto3" json:"forward_msat,omitempty"` + OutgoingCltvValue uint32 `protobuf:"varint,4,opt,name=outgoing_cltv_value,json=outgoingCltvValue,proto3" json:"outgoing_cltv_value,omitempty"` + SharedSecret string `protobuf:"bytes,5,opt,name=shared_secret,json=sharedSecret,proto3" json:"shared_secret,omitempty"` + NextOnion string `protobuf:"bytes,6,opt,name=next_onion,json=nextOnion,proto3" json:"next_onion,omitempty"` +} + +func (x *Onion) Reset() { + *x = Onion{} + if protoimpl.UnsafeEnabled { + mi := &file_cln_plugin_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Onion) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Onion) ProtoMessage() {} + +func (x *Onion) ProtoReflect() protoreflect.Message { + mi := &file_cln_plugin_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Onion.ProtoReflect.Descriptor instead. +func (*Onion) Descriptor() ([]byte, []int) { + return file_cln_plugin_proto_rawDescGZIP(), []int{1} +} + +func (x *Onion) GetPayload() string { + if x != nil { + return x.Payload + } + return "" +} + +func (x *Onion) GetShortChannelId() string { + if x != nil { + return x.ShortChannelId + } + return "" +} + +func (x *Onion) GetForwardMsat() uint64 { + if x != nil { + return x.ForwardMsat + } + return 0 +} + +func (x *Onion) GetOutgoingCltvValue() uint32 { + if x != nil { + return x.OutgoingCltvValue + } + return 0 +} + +func (x *Onion) GetSharedSecret() string { + if x != nil { + return x.SharedSecret + } + return "" +} + +func (x *Onion) GetNextOnion() string { + if x != nil { + return x.NextOnion + } + return "" +} + +type Htlc struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ShortChannelId string `protobuf:"bytes,1,opt,name=short_channel_id,json=shortChannelId,proto3" json:"short_channel_id,omitempty"` + Id uint64 `protobuf:"varint,2,opt,name=id,proto3" json:"id,omitempty"` + AmountMsat uint64 `protobuf:"varint,3,opt,name=amount_msat,json=amountMsat,proto3" json:"amount_msat,omitempty"` + CltvExpiry uint32 `protobuf:"varint,4,opt,name=cltv_expiry,json=cltvExpiry,proto3" json:"cltv_expiry,omitempty"` + CltvExpiryRelative uint32 `protobuf:"varint,5,opt,name=cltv_expiry_relative,json=cltvExpiryRelative,proto3" json:"cltv_expiry_relative,omitempty"` + PaymentHash string `protobuf:"bytes,6,opt,name=payment_hash,json=paymentHash,proto3" json:"payment_hash,omitempty"` +} + +func (x *Htlc) Reset() { + *x = Htlc{} + if protoimpl.UnsafeEnabled { + mi := &file_cln_plugin_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Htlc) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Htlc) ProtoMessage() {} + +func (x *Htlc) ProtoReflect() protoreflect.Message { + mi := &file_cln_plugin_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Htlc.ProtoReflect.Descriptor instead. +func (*Htlc) Descriptor() ([]byte, []int) { + return file_cln_plugin_proto_rawDescGZIP(), []int{2} +} + +func (x *Htlc) GetShortChannelId() string { + if x != nil { + return x.ShortChannelId + } + return "" +} + +func (x *Htlc) GetId() uint64 { + if x != nil { + return x.Id + } + return 0 +} + +func (x *Htlc) GetAmountMsat() uint64 { + if x != nil { + return x.AmountMsat + } + return 0 +} + +func (x *Htlc) GetCltvExpiry() uint32 { + if x != nil { + return x.CltvExpiry + } + return 0 +} + +func (x *Htlc) GetCltvExpiryRelative() uint32 { + if x != nil { + return x.CltvExpiryRelative + } + return 0 +} + +func (x *Htlc) GetPaymentHash() string { + if x != nil { + return x.PaymentHash + } + return "" +} + +type HtlcResolution struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Correlationid string `protobuf:"bytes,1,opt,name=correlationid,proto3" json:"correlationid,omitempty"` + // Types that are assignable to Outcome: + // *HtlcResolution_Fail + // *HtlcResolution_Continue + // *HtlcResolution_Resolve + Outcome isHtlcResolution_Outcome `protobuf_oneof:"outcome"` +} + +func (x *HtlcResolution) Reset() { + *x = HtlcResolution{} + if protoimpl.UnsafeEnabled { + mi := &file_cln_plugin_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *HtlcResolution) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HtlcResolution) ProtoMessage() {} + +func (x *HtlcResolution) ProtoReflect() protoreflect.Message { + mi := &file_cln_plugin_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HtlcResolution.ProtoReflect.Descriptor instead. +func (*HtlcResolution) Descriptor() ([]byte, []int) { + return file_cln_plugin_proto_rawDescGZIP(), []int{3} +} + +func (x *HtlcResolution) GetCorrelationid() string { + if x != nil { + return x.Correlationid + } + return "" +} + +func (m *HtlcResolution) GetOutcome() isHtlcResolution_Outcome { + if m != nil { + return m.Outcome + } + return nil +} + +func (x *HtlcResolution) GetFail() *HtlcFail { + if x, ok := x.GetOutcome().(*HtlcResolution_Fail); ok { + return x.Fail + } + return nil +} + +func (x *HtlcResolution) GetContinue() *HtlcContinue { + if x, ok := x.GetOutcome().(*HtlcResolution_Continue); ok { + return x.Continue + } + return nil +} + +func (x *HtlcResolution) GetResolve() *HtlcResolve { + if x, ok := x.GetOutcome().(*HtlcResolution_Resolve); ok { + return x.Resolve + } + return nil +} + +type isHtlcResolution_Outcome interface { + isHtlcResolution_Outcome() +} + +type HtlcResolution_Fail struct { + Fail *HtlcFail `protobuf:"bytes,2,opt,name=fail,proto3,oneof"` +} + +type HtlcResolution_Continue struct { + Continue *HtlcContinue `protobuf:"bytes,3,opt,name=continue,proto3,oneof"` +} + +type HtlcResolution_Resolve struct { + Resolve *HtlcResolve `protobuf:"bytes,4,opt,name=resolve,proto3,oneof"` +} + +func (*HtlcResolution_Fail) isHtlcResolution_Outcome() {} + +func (*HtlcResolution_Continue) isHtlcResolution_Outcome() {} + +func (*HtlcResolution_Resolve) isHtlcResolution_Outcome() {} + +type HtlcContinue struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Payload *string `protobuf:"bytes,1,opt,name=payload,proto3,oneof" json:"payload,omitempty"` + ForwardTo *string `protobuf:"bytes,2,opt,name=forward_to,json=forwardTo,proto3,oneof" json:"forward_to,omitempty"` +} + +func (x *HtlcContinue) Reset() { + *x = HtlcContinue{} + if protoimpl.UnsafeEnabled { + mi := &file_cln_plugin_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *HtlcContinue) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HtlcContinue) ProtoMessage() {} + +func (x *HtlcContinue) ProtoReflect() protoreflect.Message { + mi := &file_cln_plugin_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HtlcContinue.ProtoReflect.Descriptor instead. +func (*HtlcContinue) Descriptor() ([]byte, []int) { + return file_cln_plugin_proto_rawDescGZIP(), []int{4} +} + +func (x *HtlcContinue) GetPayload() string { + if x != nil && x.Payload != nil { + return *x.Payload + } + return "" +} + +func (x *HtlcContinue) GetForwardTo() string { + if x != nil && x.ForwardTo != nil { + return *x.ForwardTo + } + return "" +} + +type HtlcFail struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Types that are assignable to Failure: + // *HtlcFail_FailureMessage + // *HtlcFail_FailureOnion + Failure isHtlcFail_Failure `protobuf_oneof:"failure"` +} + +func (x *HtlcFail) Reset() { + *x = HtlcFail{} + if protoimpl.UnsafeEnabled { + mi := &file_cln_plugin_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *HtlcFail) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HtlcFail) ProtoMessage() {} + +func (x *HtlcFail) ProtoReflect() protoreflect.Message { + mi := &file_cln_plugin_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HtlcFail.ProtoReflect.Descriptor instead. +func (*HtlcFail) Descriptor() ([]byte, []int) { + return file_cln_plugin_proto_rawDescGZIP(), []int{5} +} + +func (m *HtlcFail) GetFailure() isHtlcFail_Failure { + if m != nil { + return m.Failure + } + return nil +} + +func (x *HtlcFail) GetFailureMessage() string { + if x, ok := x.GetFailure().(*HtlcFail_FailureMessage); ok { + return x.FailureMessage + } + return "" +} + +func (x *HtlcFail) GetFailureOnion() string { + if x, ok := x.GetFailure().(*HtlcFail_FailureOnion); ok { + return x.FailureOnion + } + return "" +} + +type isHtlcFail_Failure interface { + isHtlcFail_Failure() +} + +type HtlcFail_FailureMessage struct { + FailureMessage string `protobuf:"bytes,1,opt,name=failure_message,json=failureMessage,proto3,oneof"` +} + +type HtlcFail_FailureOnion struct { + FailureOnion string `protobuf:"bytes,2,opt,name=failure_onion,json=failureOnion,proto3,oneof"` +} + +func (*HtlcFail_FailureMessage) isHtlcFail_Failure() {} + +func (*HtlcFail_FailureOnion) isHtlcFail_Failure() {} + +type HtlcResolve struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + PaymentKey string `protobuf:"bytes,1,opt,name=payment_key,json=paymentKey,proto3" json:"payment_key,omitempty"` +} + +func (x *HtlcResolve) Reset() { + *x = HtlcResolve{} + if protoimpl.UnsafeEnabled { + mi := &file_cln_plugin_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *HtlcResolve) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HtlcResolve) ProtoMessage() {} + +func (x *HtlcResolve) ProtoReflect() protoreflect.Message { + mi := &file_cln_plugin_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HtlcResolve.ProtoReflect.Descriptor instead. +func (*HtlcResolve) Descriptor() ([]byte, []int) { + return file_cln_plugin_proto_rawDescGZIP(), []int{6} +} + +func (x *HtlcResolve) GetPaymentKey() string { + if x != nil { + return x.PaymentKey + } + return "" +} + +var File_cln_plugin_proto protoreflect.FileDescriptor + +var file_cln_plugin_proto_rawDesc = []byte{ + 0x0a, 0x10, 0x63, 0x6c, 0x6e, 0x5f, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x22, 0x8c, 0x01, 0x0a, 0x0c, 0x48, 0x74, 0x6c, 0x63, 0x41, 0x63, 0x63, 0x65, 0x70, + 0x74, 0x65, 0x64, 0x12, 0x24, 0x0a, 0x0d, 0x63, 0x6f, 0x72, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x63, 0x6f, 0x72, 0x72, + 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x69, 0x64, 0x12, 0x1c, 0x0a, 0x05, 0x6f, 0x6e, 0x69, + 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x06, 0x2e, 0x4f, 0x6e, 0x69, 0x6f, 0x6e, + 0x52, 0x05, 0x6f, 0x6e, 0x69, 0x6f, 0x6e, 0x12, 0x19, 0x0a, 0x04, 0x68, 0x74, 0x6c, 0x63, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x05, 0x2e, 0x48, 0x74, 0x6c, 0x63, 0x52, 0x04, 0x68, 0x74, + 0x6c, 0x63, 0x12, 0x1d, 0x0a, 0x0a, 0x66, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x5f, 0x74, 0x6f, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x66, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x54, + 0x6f, 0x22, 0xe2, 0x01, 0x0a, 0x05, 0x4f, 0x6e, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x70, + 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x61, + 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x12, 0x28, 0x0a, 0x10, 0x73, 0x68, 0x6f, 0x72, 0x74, 0x5f, 0x63, + 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0e, 0x73, 0x68, 0x6f, 0x72, 0x74, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x49, 0x64, 0x12, + 0x21, 0x0a, 0x0c, 0x66, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x66, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x4d, 0x73, + 0x61, 0x74, 0x12, 0x2e, 0x0a, 0x13, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x5f, 0x63, + 0x6c, 0x74, 0x76, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, + 0x11, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x43, 0x6c, 0x74, 0x76, 0x56, 0x61, 0x6c, + 0x75, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x5f, 0x73, 0x65, 0x63, + 0x72, 0x65, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x73, 0x68, 0x61, 0x72, 0x65, + 0x64, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x6e, 0x65, 0x78, 0x74, 0x5f, + 0x6f, 0x6e, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6e, 0x65, 0x78, + 0x74, 0x4f, 0x6e, 0x69, 0x6f, 0x6e, 0x22, 0xd7, 0x01, 0x0a, 0x04, 0x48, 0x74, 0x6c, 0x63, 0x12, + 0x28, 0x0a, 0x10, 0x73, 0x68, 0x6f, 0x72, 0x74, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, + 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x73, 0x68, 0x6f, 0x72, 0x74, + 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x49, 0x64, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x6d, 0x6f, + 0x75, 0x6e, 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, + 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x6c, + 0x74, 0x76, 0x5f, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, + 0x0a, 0x63, 0x6c, 0x74, 0x76, 0x45, 0x78, 0x70, 0x69, 0x72, 0x79, 0x12, 0x30, 0x0a, 0x14, 0x63, + 0x6c, 0x74, 0x76, 0x5f, 0x65, 0x78, 0x70, 0x69, 0x72, 0x79, 0x5f, 0x72, 0x65, 0x6c, 0x61, 0x74, + 0x69, 0x76, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x12, 0x63, 0x6c, 0x74, 0x76, 0x45, + 0x78, 0x70, 0x69, 0x72, 0x79, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x76, 0x65, 0x12, 0x21, 0x0a, + 0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x06, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0b, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x48, 0x61, 0x73, 0x68, + 0x22, 0xb9, 0x01, 0x0a, 0x0e, 0x48, 0x74, 0x6c, 0x63, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, + 0x69, 0x6f, 0x6e, 0x12, 0x24, 0x0a, 0x0d, 0x63, 0x6f, 0x72, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x63, 0x6f, 0x72, 0x72, + 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x69, 0x64, 0x12, 0x1f, 0x0a, 0x04, 0x66, 0x61, 0x69, + 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x09, 0x2e, 0x48, 0x74, 0x6c, 0x63, 0x46, 0x61, + 0x69, 0x6c, 0x48, 0x00, 0x52, 0x04, 0x66, 0x61, 0x69, 0x6c, 0x12, 0x2b, 0x0a, 0x08, 0x63, 0x6f, + 0x6e, 0x74, 0x69, 0x6e, 0x75, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x48, + 0x74, 0x6c, 0x63, 0x43, 0x6f, 0x6e, 0x74, 0x69, 0x6e, 0x75, 0x65, 0x48, 0x00, 0x52, 0x08, 0x63, + 0x6f, 0x6e, 0x74, 0x69, 0x6e, 0x75, 0x65, 0x12, 0x28, 0x0a, 0x07, 0x72, 0x65, 0x73, 0x6f, 0x6c, + 0x76, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x48, 0x74, 0x6c, 0x63, 0x52, + 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65, 0x48, 0x00, 0x52, 0x07, 0x72, 0x65, 0x73, 0x6f, 0x6c, 0x76, + 0x65, 0x42, 0x09, 0x0a, 0x07, 0x6f, 0x75, 0x74, 0x63, 0x6f, 0x6d, 0x65, 0x22, 0x6c, 0x0a, 0x0c, + 0x48, 0x74, 0x6c, 0x63, 0x43, 0x6f, 0x6e, 0x74, 0x69, 0x6e, 0x75, 0x65, 0x12, 0x1d, 0x0a, 0x07, + 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, + 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x88, 0x01, 0x01, 0x12, 0x22, 0x0a, 0x0a, 0x66, + 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x5f, 0x74, 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, + 0x01, 0x52, 0x09, 0x66, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x54, 0x6f, 0x88, 0x01, 0x01, 0x42, + 0x0a, 0x0a, 0x08, 0x5f, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x42, 0x0d, 0x0a, 0x0b, 0x5f, + 0x66, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x5f, 0x74, 0x6f, 0x22, 0x67, 0x0a, 0x08, 0x48, 0x74, + 0x6c, 0x63, 0x46, 0x61, 0x69, 0x6c, 0x12, 0x29, 0x0a, 0x0f, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, + 0x65, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x48, + 0x00, 0x52, 0x0e, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x12, 0x25, 0x0a, 0x0d, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x5f, 0x6f, 0x6e, 0x69, + 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0c, 0x66, 0x61, 0x69, 0x6c, + 0x75, 0x72, 0x65, 0x4f, 0x6e, 0x69, 0x6f, 0x6e, 0x42, 0x09, 0x0a, 0x07, 0x66, 0x61, 0x69, 0x6c, + 0x75, 0x72, 0x65, 0x22, 0x2e, 0x0a, 0x0b, 0x48, 0x74, 0x6c, 0x63, 0x52, 0x65, 0x73, 0x6f, 0x6c, + 0x76, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x6b, 0x65, + 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, + 0x4b, 0x65, 0x79, 0x32, 0x3d, 0x0a, 0x09, 0x43, 0x6c, 0x6e, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, + 0x12, 0x30, 0x0a, 0x0a, 0x48, 0x74, 0x6c, 0x63, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x0f, + 0x2e, 0x48, 0x74, 0x6c, 0x63, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x1a, + 0x0d, 0x2e, 0x48, 0x74, 0x6c, 0x63, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x28, 0x01, + 0x30, 0x01, 0x42, 0x28, 0x5a, 0x26, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, + 0x2f, 0x62, 0x72, 0x65, 0x65, 0x7a, 0x2f, 0x6c, 0x73, 0x70, 0x64, 0x2f, 0x63, 0x6c, 0x6e, 0x5f, + 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_cln_plugin_proto_rawDescOnce sync.Once + file_cln_plugin_proto_rawDescData = file_cln_plugin_proto_rawDesc +) + +func file_cln_plugin_proto_rawDescGZIP() []byte { + file_cln_plugin_proto_rawDescOnce.Do(func() { + file_cln_plugin_proto_rawDescData = protoimpl.X.CompressGZIP(file_cln_plugin_proto_rawDescData) + }) + return file_cln_plugin_proto_rawDescData +} + +var file_cln_plugin_proto_msgTypes = make([]protoimpl.MessageInfo, 7) +var file_cln_plugin_proto_goTypes = []interface{}{ + (*HtlcAccepted)(nil), // 0: HtlcAccepted + (*Onion)(nil), // 1: Onion + (*Htlc)(nil), // 2: Htlc + (*HtlcResolution)(nil), // 3: HtlcResolution + (*HtlcContinue)(nil), // 4: HtlcContinue + (*HtlcFail)(nil), // 5: HtlcFail + (*HtlcResolve)(nil), // 6: HtlcResolve +} +var file_cln_plugin_proto_depIdxs = []int32{ + 1, // 0: HtlcAccepted.onion:type_name -> Onion + 2, // 1: HtlcAccepted.htlc:type_name -> Htlc + 5, // 2: HtlcResolution.fail:type_name -> HtlcFail + 4, // 3: HtlcResolution.continue:type_name -> HtlcContinue + 6, // 4: HtlcResolution.resolve:type_name -> HtlcResolve + 3, // 5: ClnPlugin.HtlcStream:input_type -> HtlcResolution + 0, // 6: ClnPlugin.HtlcStream:output_type -> HtlcAccepted + 6, // [6:7] is the sub-list for method output_type + 5, // [5:6] is the sub-list for method input_type + 5, // [5:5] is the sub-list for extension type_name + 5, // [5:5] is the sub-list for extension extendee + 0, // [0:5] is the sub-list for field type_name +} + +func init() { file_cln_plugin_proto_init() } +func file_cln_plugin_proto_init() { + if File_cln_plugin_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_cln_plugin_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*HtlcAccepted); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_cln_plugin_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Onion); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_cln_plugin_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Htlc); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_cln_plugin_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*HtlcResolution); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_cln_plugin_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*HtlcContinue); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_cln_plugin_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*HtlcFail); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_cln_plugin_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*HtlcResolve); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + file_cln_plugin_proto_msgTypes[3].OneofWrappers = []interface{}{ + (*HtlcResolution_Fail)(nil), + (*HtlcResolution_Continue)(nil), + (*HtlcResolution_Resolve)(nil), + } + file_cln_plugin_proto_msgTypes[4].OneofWrappers = []interface{}{} + file_cln_plugin_proto_msgTypes[5].OneofWrappers = []interface{}{ + (*HtlcFail_FailureMessage)(nil), + (*HtlcFail_FailureOnion)(nil), + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_cln_plugin_proto_rawDesc, + NumEnums: 0, + NumMessages: 7, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_cln_plugin_proto_goTypes, + DependencyIndexes: file_cln_plugin_proto_depIdxs, + MessageInfos: file_cln_plugin_proto_msgTypes, + }.Build() + File_cln_plugin_proto = out.File + file_cln_plugin_proto_rawDesc = nil + file_cln_plugin_proto_goTypes = nil + file_cln_plugin_proto_depIdxs = nil +} diff --git a/cln_plugin/proto/cln_plugin.proto b/cln_plugin/proto/cln_plugin.proto index f284d40..be35059 100644 --- a/cln_plugin/proto/cln_plugin.proto +++ b/cln_plugin/proto/cln_plugin.proto @@ -1,46 +1,56 @@ syntax = "proto3"; -option go_package="github.com/breez/lspd/cln_plugin"; +option go_package="github.com/breez/lspd/cln_plugin/proto"; service ClnPlugin { rpc HtlcStream(stream HtlcResolution) returns (stream HtlcAccepted); } message HtlcAccepted { - uint64 correlationid = 1; + string correlationid = 1; Onion onion = 2; - HtlcOffer htlc = 3; + Htlc htlc = 3; + string forward_to = 4; } message Onion { - bytes payload = 1; - uint64 short_channel_id = 2; - uint64 forward_amount_msat = 3; + string payload = 1; + string short_channel_id = 2; + uint64 forward_msat = 3; + uint32 outgoing_cltv_value = 4; + string shared_secret = 5; + string next_onion = 6; } -message HtlcOffer { - uint64 amount_msat = 2; - uint32 cltv_expiry_relative = 3; +message Htlc { + string short_channel_id = 1; + uint64 id = 2; + uint64 amount_msat = 3; uint32 cltv_expiry = 4; - bytes payment_hash = 5; + uint32 cltv_expiry_relative = 5; + string payment_hash = 6; } message HtlcResolution { - uint64 correlationid = 1; + string correlationid = 1; oneof outcome { HtlcFail fail = 2; HtlcContinue continue = 3; - HtlcContinueWith continue_with = 4; + HtlcResolve resolve = 4; } } -message HtlcFail { - bytes failure_message = 1; -} - message HtlcContinue { + optional string payload = 1; + optional string forward_to = 2; } -message HtlcContinueWith { - bytes channel_id = 1; - bytes payload = 2; -} \ No newline at end of file +message HtlcFail { + oneof failure { + string failure_message = 1; + string failure_onion = 2; + } +} + +message HtlcResolve { + string payment_key = 1; +} diff --git a/cln_plugin/cln_plugin_grpc.pb.go b/cln_plugin/proto/cln_plugin_grpc.pb.go similarity index 98% rename from cln_plugin/cln_plugin_grpc.pb.go rename to cln_plugin/proto/cln_plugin_grpc.pb.go index 3d4c9f9..1f8ccd2 100644 --- a/cln_plugin/cln_plugin_grpc.pb.go +++ b/cln_plugin/proto/cln_plugin_grpc.pb.go @@ -1,10 +1,10 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: // - protoc-gen-go-grpc v1.2.0 -// - protoc v3.21.8 +// - protoc v3.21.12 // source: cln_plugin.proto -package cln_plugin +package proto import ( context "context" diff --git a/cln_plugin/sample.env b/cln_plugin/sample.env deleted file mode 100644 index 1dc0012..0000000 --- a/cln_plugin/sample.env +++ /dev/null @@ -1 +0,0 @@ -LISTEN_ADDRESS=
\ No newline at end of file diff --git a/cln_plugin/server.go b/cln_plugin/server.go index 7bf91ea..680b83a 100644 --- a/cln_plugin/server.go +++ b/cln_plugin/server.go @@ -8,64 +8,103 @@ import ( "sync" "time" + "github.com/breez/lspd/cln_plugin/proto" grpc "google.golang.org/grpc" ) -var receiveWaitDelay = time.Millisecond * 200 - +// A subscription represents a grpc client that is connected to the server. type subscription struct { - stream ClnPlugin_HtlcStreamServer + stream proto.ClnPlugin_HtlcStreamServer done chan struct{} -} -type server struct { - ClnPluginServer - listenAddress string - grpcServer *grpc.Server - startMtx sync.Mutex - corrMtx sync.Mutex - subscription *subscription - newSubscriber chan struct{} - done chan struct{} - correlations map[uint64]chan *HtlcResolution - index uint64 + err chan error } -func NewServer(listenAddress string) *server { +// Internal htlc_accepted message meant for the sendQueue. +type htlcAcceptedMsg struct { + id string + htlc *HtlcAccepted + timeout <-chan time.Time +} + +// Internal htlc result message meant for the recvQueue. +type htlcResultMsg struct { + id string + result interface{} +} + +type server struct { + proto.ClnPluginServer + listenAddress string + subscriberTimeout time.Duration + grpcServer *grpc.Server + mtx sync.Mutex + subscription *subscription + newSubscriber chan struct{} + started chan struct{} + startError chan error + done chan struct{} + sendQueue chan *htlcAcceptedMsg + recvQueue chan *htlcResultMsg +} + +// Creates a new grpc server +func NewServer(listenAddress string, subscriberTimeout time.Duration) *server { + // TODO: Set a sane max queue size return &server{ - listenAddress: listenAddress, - newSubscriber: make(chan struct{}, 1), - correlations: make(map[uint64]chan *HtlcResolution), - index: 0, + listenAddress: listenAddress, + subscriberTimeout: subscriberTimeout, + sendQueue: make(chan *htlcAcceptedMsg, 10000), + recvQueue: make(chan *htlcResultMsg, 10000), + started: make(chan struct{}), + startError: make(chan error, 1), } } +// Starts the grpc server. Blocks until the servver is stopped. WaitStarted can +// be called to ensure the server is started without errors if this function +// is run as a goroutine. func (s *server) Start() error { - s.startMtx.Lock() + s.mtx.Lock() if s.grpcServer != nil { - s.startMtx.Unlock() + s.mtx.Unlock() return nil } lis, err := net.Listen("tcp", s.listenAddress) if err != nil { log.Printf("ERROR Server failed to listen: %v", err) - s.startMtx.Unlock() + s.startError <- err + s.mtx.Unlock() return err } s.done = make(chan struct{}) + s.newSubscriber = make(chan struct{}) s.grpcServer = grpc.NewServer() - s.startMtx.Unlock() - RegisterClnPluginServer(s.grpcServer, s) + s.mtx.Unlock() + proto.RegisterClnPluginServer(s.grpcServer, s) log.Printf("Server starting to listen on %s.", s.listenAddress) + go s.listenHtlcRequests() go s.listenHtlcResponses() + close(s.started) return s.grpcServer.Serve(lis) } +// Waits until the server has started, or errored during startup. +func (s *server) WaitStarted() error { + select { + case <-s.started: + return nil + case err := <-s.startError: + return err + } +} + +// Stops all work from the grpc server immediately. func (s *server) Stop() { - s.startMtx.Lock() - defer s.startMtx.Unlock() + s.mtx.Lock() + defer s.mtx.Unlock() log.Printf("Server Stop() called.") if s.grpcServer == nil { return @@ -76,36 +115,38 @@ func (s *server) Stop() { s.grpcServer = nil } -func (s *server) HtlcStream(stream ClnPlugin_HtlcStreamServer) error { +// Grpc method that is called when a new client subscribes. There can only be +// one subscriber active at a time. If there is an error receiving or sending +// from or to the subscriber, the subscription is closed. +func (s *server) HtlcStream(stream proto.ClnPlugin_HtlcStreamServer) error { log.Printf("Got HTLC stream subscription request.") - s.startMtx.Lock() + s.mtx.Lock() if s.subscription != nil { - s.startMtx.Unlock() + s.mtx.Unlock() return fmt.Errorf("already subscribed") } sb := &subscription{ stream: stream, done: make(chan struct{}), + err: make(chan error, 1), } s.subscription = sb - s.newSubscriber <- struct{}{} - s.startMtx.Unlock() + + // Notify listeners that a new subscriber is active. Replace the chan with + // a new one immediately in case this subscriber is dropped later. + close(s.newSubscriber) + s.newSubscriber = make(chan struct{}) + s.mtx.Unlock() defer func() { - s.startMtx.Lock() - s.subscription = nil - close(sb.done) - s.startMtx.Unlock() + s.removeSubscriptionIfUnchanged(sb, nil) }() go func() { <-stream.Context().Done() log.Printf("HtlcStream context is done. Removing subscriber: %v", stream.Context().Err()) - s.startMtx.Lock() - s.subscription = nil - close(sb.done) - s.startMtx.Unlock() + s.removeSubscriptionIfUnchanged(sb, stream.Context().Err()) }() select { @@ -115,82 +156,179 @@ func (s *server) HtlcStream(stream ClnPlugin_HtlcStreamServer) error { case <-sb.done: log.Printf("HTLC stream signalled done. Return EOF.") return io.EOF + case err := <-sb.err: + log.Printf("HTLC stream signalled error. Return %v", err) + return err } } -func (s *server) Send(h *HtlcAccepted) *HtlcResolution { - sb := s.subscription - if sb == nil { - log.Printf("No subscribers available. Ignoring HtlcAccepted %+v", h) - return s.defaultResolution() - } - - c := make(chan *HtlcResolution) - s.corrMtx.Lock() - s.index++ - index := s.index - s.correlations[index] = c - s.corrMtx.Unlock() - - h.Correlationid = index - - defer func() { - s.corrMtx.Lock() - delete(s.correlations, index) - s.corrMtx.Unlock() - close(c) - }() - - log.Printf("Sending HtlcAccepted: %+v", h) - err := sb.stream.Send(h) - if err != nil { - // TODO: Close the connection? Reset the subscriber? - log.Printf("Send() errored, Correlationid: %d: %v", index, err) - return s.defaultResolution() +// Enqueues a htlc_accepted message for send to the grpc client. +func (s *server) Send(id string, h *HtlcAccepted) { + s.sendQueue <- &htlcAcceptedMsg{ + id: id, + htlc: h, + timeout: time.After(s.subscriberTimeout), } +} +// Receives the next htlc resolution message from the grpc client. Returns id +// and message. Blocks until a message is available. Returns a nil message if +// the server is done. +func (s *server) Receive() (string, interface{}) { select { case <-s.done: - log.Printf("Signalled done while waiting for htlc resolution, Correlationid: %d, Ignoring: %+v", index, h) - return s.defaultResolution() - case resolution := <-c: - log.Printf("Got resolution, Correlationid: %d: %+v", index, h) - return resolution + return "", nil + case msg := <-s.recvQueue: + return msg.id, msg.result } } -func (s *server) recv() *HtlcResolution { +// Helper function that blocks until a message from a grpc client is received +// or the server stops. Either returns a received message, or nil if the server +// has stopped. +func (s *server) recv() *proto.HtlcResolution { for { + // make a copy of the used fields, to make sure state updates don't + // surprise us. The newSubscriber chan is swapped whenever a new + // subscriber arrives. + s.mtx.Lock() sb := s.subscription + ns := s.newSubscriber + s.mtx.Unlock() + if sb == nil { log.Printf("Got no subscribers for receive. Waiting for subscriber.") select { case <-s.done: log.Printf("Done signalled, stopping receive.") - return s.defaultResolution() - case <-s.newSubscriber: + return nil + case <-ns: log.Printf("New subscription available for receive, continue receive.") continue } } + // There is a subscription active. Attempt to receive a message. r, err := sb.stream.Recv() if err == nil { log.Printf("Received HtlcResolution %+v", r) return r } - // TODO: close the subscription?? - log.Printf("Recv() errored, waiting %v: %v", receiveWaitDelay, err) + // Receiving the message failed, so the subscription is broken. Remove + // it if it hasn't been updated already. We'll try receiving again in + // the next iteration of the for loop. + log.Printf("Recv() errored, removing subscription: %v", err) + s.removeSubscriptionIfUnchanged(sb, err) + } +} + +// Stops and removes the subscription if this is the currently active +// subscription. If the subscription was changed in the meantime, this function +// does nothing. +func (s *server) removeSubscriptionIfUnchanged(sb *subscription, err error) { + s.mtx.Lock() + // If the subscription reference hasn't changed yet in the meantime, kill it. + if s.subscription == sb { + if err == nil { + close(sb.done) + } else { + sb.err <- err + } + s.subscription = nil + } + s.mtx.Unlock() +} + +// Listens to sendQueue for htlc_accepted requests from cln. The message will be +// held until a subscriber is active, or the subscriber timeout expires. The +// messages are sent to the grpc client in fifo order. +func (s *server) listenHtlcRequests() { + for { select { case <-s.done: - log.Printf("Done signalled, stopping receive.") - return s.defaultResolution() - case <-time.After(receiveWaitDelay): + log.Printf("listenHtlcRequests received done. Stop listening.") + return + case msg := <-s.sendQueue: + s.handleHtlcAccepted(msg) } } } +// Attempts to send a htlc_accepted message to the grpc client. The message will +// be held until a subscriber is active, or the subscriber timeout expires. +func (s *server) handleHtlcAccepted(msg *htlcAcceptedMsg) { + for { + s.mtx.Lock() + sb := s.subscription + ns := s.newSubscriber + s.mtx.Unlock() + + // If there is no active subscription, wait until there is a new + // subscriber, or the message times out. + if sb == nil { + select { + case <-s.done: + log.Printf("handleHtlcAccepted received server done. Stop processing.") + return + case <-ns: + log.Printf("got a new subscriber. continue handleHtlcAccepted.") + continue + case <-msg.timeout: + log.Printf( + "WARNING: htlc with id '%s' timed out after '%v' waiting "+ + "for grpc subscriber: %+v", + msg.id, + s.subscriberTimeout, + msg.htlc, + ) + s.recvQueue <- &htlcResultMsg{ + id: msg.id, + result: s.defaultResult(), + } + + return + } + } + + // There is a subscriber. Attempt to send the htlc_accepted message. + err := sb.stream.Send(&proto.HtlcAccepted{ + Correlationid: msg.id, + Onion: &proto.Onion{ + Payload: msg.htlc.Onion.Payload, + ShortChannelId: msg.htlc.Onion.ShortChannelId, + ForwardMsat: msg.htlc.Onion.ForwardMsat, + OutgoingCltvValue: msg.htlc.Onion.OutgoingCltvValue, + SharedSecret: msg.htlc.Onion.SharedSecret, + NextOnion: msg.htlc.Onion.NextOnion, + }, + Htlc: &proto.Htlc{ + ShortChannelId: msg.htlc.Htlc.ShortChannelId, + Id: msg.htlc.Htlc.Id, + AmountMsat: msg.htlc.Htlc.AmountMsat, + CltvExpiry: msg.htlc.Htlc.CltvExpiry, + CltvExpiryRelative: msg.htlc.Htlc.CltvExpiryRelative, + PaymentHash: msg.htlc.Htlc.PaymentHash, + }, + ForwardTo: msg.htlc.ForwardTo, + }) + + // If there is no error, we're done. + if err == nil { + return + } + + // If we end up here, there was an error sending the message to the + // grpc client. + log.Printf("Error sending htlc_accepted message to subscriber. "+ + "Removing subscription: %v", err) + s.removeSubscriptionIfUnchanged(sb, err) + } +} + +// Listens to htlc responses from the grpc client and appends them to the +// receive queue. The messages from the receive queue are read in the Receive +// function. func (s *server) listenHtlcResponses() { for { select { @@ -198,23 +336,77 @@ func (s *server) listenHtlcResponses() { log.Printf("listenHtlcResponses received done. Stopping listening.") return default: - response := s.recv() - s.corrMtx.Lock() - correlation, ok := s.correlations[response.Correlationid] - s.corrMtx.Unlock() - if ok { - correlation <- response - } else { - log.Printf("Got HTLC resolution that could not be correlated: %+v", response) + resp := s.recv() + s.recvQueue <- &htlcResultMsg{ + id: resp.Correlationid, + result: s.mapResult(resp.Outcome), } } } } -func (s *server) defaultResolution() *HtlcResolution { - return &HtlcResolution{ - Outcome: &HtlcResolution_Continue{ - Continue: &HtlcContinue{}, - }, +// Maps a grpc result to the corresponding result for cln. The cln message +// is a raw json message, so it's easiest to use a map directly. +func (s *server) mapResult(outcome interface{}) interface{} { + // result: continue + cont, ok := outcome.(*proto.HtlcResolution_Continue) + if ok { + result := map[string]interface{}{ + "result": "continue", + } + + if cont.Continue.ForwardTo != nil { + result["forward_to"] = *cont.Continue.ForwardTo + } + + if cont.Continue.Payload != nil { + result["payload"] = *cont.Continue.Payload + } + + return result + } + + // result: fail + fail, ok := outcome.(*proto.HtlcResolution_Fail) + if ok { + result := map[string]interface{}{ + "result": "fail", + } + + fm, ok := fail.Fail.Failure.(*proto.HtlcFail_FailureMessage) + if ok { + result["failure_message"] = fm.FailureMessage + } + + fo, ok := fail.Fail.Failure.(*proto.HtlcFail_FailureOnion) + if ok { + result["failure_onion"] = fo.FailureOnion + } + + return result + } + + // result: resolve + resolve, ok := outcome.(*proto.HtlcResolution_Resolve) + if ok { + result := map[string]interface{}{ + "result": "resolve", + "payment_key": resolve.Resolve.PaymentKey, + } + + return result + } + + // On an unknown result we haven't implemented all possible cases from the + // grpc message. We don't understand what's going on, so we'll return + // result: continue. + log.Printf("Unexpected htlc resolution type %T: %+v", outcome, outcome) + return s.defaultResult() +} + +// Returns a result: continue message. +func (s *server) defaultResult() interface{} { + return map[string]interface{}{ + "result": "continue", } } diff --git a/itest/cln_lspd_node.go b/itest/cln_lspd_node.go index cdbfdc8..c8731d3 100644 --- a/itest/cln_lspd_node.go +++ b/itest/cln_lspd_node.go @@ -30,7 +30,6 @@ type ClnLspNode struct { isInitialized bool mtx sync.Mutex pluginBinary string - pluginFile string pluginAddress string } @@ -43,7 +42,6 @@ type clnLspNodeRuntime struct { func NewClnLspdNode(h *lntest.TestHarness, m *lntest.Miner, name string) LspNode { scriptDir := h.GetDirectory("lspd") - pluginFile := filepath.Join(scriptDir, "htlc.sh") pluginBinary := *clnPluginExec pluginPort, err := lntest.GetPort() if err != nil { @@ -52,7 +50,8 @@ func NewClnLspdNode(h *lntest.TestHarness, m *lntest.Miner, name string) LspNode pluginAddress := fmt.Sprintf("127.0.0.1:%d", pluginPort) args := []string{ - fmt.Sprintf("--plugin=%s", pluginFile), + fmt.Sprintf("--plugin=%s", pluginBinary), + fmt.Sprintf("--lsp.listen=%s", pluginAddress), fmt.Sprintf("--fee-base=%d", lspBaseFeeMsat), fmt.Sprintf("--fee-per-satoshi=%d", lspFeeRatePpm), fmt.Sprintf("--cltv-delta=%d", lspCltvDelta), @@ -79,7 +78,6 @@ func NewClnLspdNode(h *lntest.TestHarness, m *lntest.Miner, name string) LspNode logFilePath: logFilePath, lspBase: lspbase, pluginBinary: pluginBinary, - pluginFile: pluginFile, pluginAddress: pluginAddress, } @@ -102,16 +100,6 @@ func (c *ClnLspNode) Start() { Name: fmt.Sprintf("%s: lsp base", c.lspBase.name), Fn: c.lspBase.Stop, }) - - pluginContent := fmt.Sprintf(`#!/bin/bash -export LISTEN_ADDRESS=%s -%s`, c.pluginAddress, c.pluginBinary) - - err = os.WriteFile(c.pluginFile, []byte(pluginContent), 0755) - if err != nil { - lntest.PerformCleanup(cleanups) - c.harness.T.Fatalf("failed create lsp cln plugin file: %v", err) - } } c.lightningNode.Start() From 38dabe5685d423e8430dbfca8e426f648c7486a0 Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Fri, 30 Dec 2022 12:01:33 +0100 Subject: [PATCH 130/214] organize code and cleanup --- cln_plugin/cln_plugin.go | 190 +++++++++++++++++++-------------------- cln_plugin/server.go | 154 +++++++++++++++++-------------- itest/postgres.go | 5 +- 3 files changed, 183 insertions(+), 166 deletions(-) diff --git a/cln_plugin/cln_plugin.go b/cln_plugin/cln_plugin.go index 987fba6..4b217f0 100644 --- a/cln_plugin/cln_plugin.go +++ b/cln_plugin/cln_plugin.go @@ -60,43 +60,14 @@ func NewClnPlugin(in, out *os.File) *ClnPlugin { // NOTE: The grpc server is started in the handleInit function. func (c *ClnPlugin) Start() { c.setupLogging() - go c.listen() + go c.listenRequests() <-c.done } -func (c *ClnPlugin) setupLogging() { - in, out := io.Pipe() - go func(in io.Reader) { - // everytime we get a new message, log it thru c-lightning - scanner := bufio.NewScanner(in) - for { - select { - case <-c.done: - return - default: - if !scanner.Scan() { - if err := scanner.Err(); err != nil { - log.Fatalf( - "can't print out to std err, killing: %v", - err, - ) - } - } - - for _, line := range strings.Split(scanner.Text(), "\n") { - c.log("info", line) - } - } - } - - }(in) - log.SetFlags(log.Ltime | log.Lshortfile) - log.SetOutput(out) -} - // Stops the cln plugin. Drops any remaining work immediately. // Pending htlcs will be replayed when cln starts again. func (c *ClnPlugin) Stop() { + log.Printf("Stop called. Stopping plugin.") close(c.done) s := c.server @@ -107,7 +78,7 @@ func (c *ClnPlugin) Stop() { // listens stdout for requests from cln and sends the requests to the // appropriate handler in fifo order. -func (c *ClnPlugin) listen() error { +func (c *ClnPlugin) listenRequests() error { scanner := bufio.NewScanner(c.in) buf := make([]byte, 1024) scanner.Buffer(buf, MaxIntakeBuffer) @@ -129,8 +100,10 @@ func (c *ClnPlugin) listen() error { } msg := scanner.Bytes() - // TODO: Pipe logs to the proper place. + + // Always log the message json log.Println(string(msg)) + // pass down a copy so things stay sane msg_buf := make([]byte, len(msg)) copy(msg_buf, msg) @@ -141,15 +114,39 @@ func (c *ClnPlugin) listen() error { } } +// Listens to responses to htlc_accepted requests from the grpc server. +func (c *ClnPlugin) listenServer() { + for { + select { + case <-c.done: + return + default: + id, result := c.server.Receive() + + // The server may return nil if it is stopped. + if result == nil { + continue + } + + serid, _ := json.Marshal(&id) + c.sendToCln(&Response{ + Id: serid, + JsonRpc: SpecVersion, + Result: result, + }) + } + } +} + // processes a single message from cln. Sends the message to the appropriate // handler. func (c *ClnPlugin) processMsg(msg []byte) { if len(msg) == 0 { - c.sendError(nil, InvalidRequest, "Invalid Request") + c.sendError(nil, InvalidRequest, "Got an invalid zero length request") return } - // Right now we don't handle arrays of requests... + // Handle request batches. if msg[0] == '[' { var requests []*Request err := json.Unmarshal(msg, &requests) @@ -157,7 +154,7 @@ func (c *ClnPlugin) processMsg(msg []byte) { c.sendError( nil, ParseError, - fmt.Sprintf("Parse error:%s [%s]", err.Error(), msg), + fmt.Sprintf("Failed to unmarshal request batch: %v", err), ) return } @@ -173,11 +170,10 @@ func (c *ClnPlugin) processMsg(msg []byte) { var request Request err := json.Unmarshal(msg, &request) if err != nil { - log.Printf("failed to unmarshal request: %v", err) c.sendError( nil, ParseError, - fmt.Sprintf("Parse error:%s [%s]", err.Error(), msg), + fmt.Sprintf("failed to unmarshal request: %v", err), ) return } @@ -188,15 +184,11 @@ func (c *ClnPlugin) processMsg(msg []byte) { func (c *ClnPlugin) processRequest(request *Request) { // Make sure the spec version is expected. if request.JsonRpc != SpecVersion { - c.sendError( - request.Id, - InvalidRequest, - fmt.Sprintf( - `Invalid jsonrpc, expected '%s' got '%s'`, - SpecVersion, - request.JsonRpc, - ), - ) + c.sendError(request.Id, InvalidRequest, fmt.Sprintf( + `Invalid jsonrpc, expected '%s' got '%s'`, + SpecVersion, + request.JsonRpc, + )) return } @@ -235,9 +227,8 @@ func (c *ClnPlugin) handleGetManifest(request *Request) { { Name: SubscriberTimeoutOption, Type: "string", - Description: "htlc timeout duration when there is no " + - "subscriber to the grpc server. golang duration " + - "string.", + Description: "the maximum duration we will hold a htlc " + + "if no subscriber is active. golang duration string.", Default: &DefaultSubscriberTimeout, }, }, @@ -266,11 +257,7 @@ func (c *ClnPlugin) handleInit(request *Request) { c.sendError( request.Id, ParseError, - fmt.Sprintf( - "Error parsing init params:%s [%s]", - err.Error(), - request.Params, - ), + fmt.Sprintf("Failed to unmarshal init params: %v", err), ) return } @@ -362,30 +349,6 @@ func (c *ClnPlugin) handleInit(request *Request) { }) } -// Listens to responses to htlc_accepted requests from the grpc server. -func (c *ClnPlugin) listenServer() { - for { - select { - case <-c.done: - return - default: - id, result := c.server.Receive() - - // The server may return nil if it is stopped. - if result == nil { - continue - } - - serid, _ := json.Marshal(&id) - c.sendToCln(&Response{ - Id: serid, - JsonRpc: SpecVersion, - Result: result, - }) - } - } -} - // Handles the shutdown message. Stops any work immediately. func (c *ClnPlugin) handleShutdown(request *Request) { c.Stop() @@ -400,7 +363,7 @@ func (c *ClnPlugin) handleHtlcAccepted(request *Request) { request.Id, ParseError, fmt.Sprintf( - "Error parsing htlc_accepted params:%s [%s]", + "Failed to unmarshal htlc_accepted params:%s [%s]", err.Error(), request.Params, ), @@ -408,11 +371,15 @@ func (c *ClnPlugin) handleHtlcAccepted(request *Request) { return } - c.server.Send(c.idToString(request.Id), &htlc) + c.server.Send(idToString(request.Id), &htlc) } // Sends an error to cln. func (c *ClnPlugin) sendError(id json.RawMessage, code int, message string) { + // Log the error to cln first. + c.log("error", message) + + // Then create an error message. resp := &Response{ JsonRpc: SpecVersion, Error: &RpcError{ @@ -428,29 +395,14 @@ func (c *ClnPlugin) sendError(id json.RawMessage, code int, message string) { c.sendToCln(resp) } -// converts a raw cln id to string. The CLN id can either be an integer or a -// string. if it's a string, the quotes are removed. -func (c *ClnPlugin) idToString(id json.RawMessage) string { - if len(id) == 0 { - return "" - } - - str := string(id) - str = strings.TrimSpace(str) - str = strings.Trim(str, "\"") - str = strings.Trim(str, "'") - return str -} - // Sends a message to cln. func (c *ClnPlugin) sendToCln(msg interface{}) { c.writeMtx.Lock() defer c.writeMtx.Unlock() - // TODO: log data, err := json.Marshal(msg) if err != nil { - log.Println(err.Error()) + log.Printf("Failed to marshal message for cln, ignoring message: %+v", msg) return } @@ -459,6 +411,36 @@ func (c *ClnPlugin) sendToCln(msg interface{}) { c.out.Flush() } +func (c *ClnPlugin) setupLogging() { + in, out := io.Pipe() + log.SetFlags(log.Ltime | log.Lshortfile) + log.SetOutput(out) + go func(in io.Reader) { + // everytime we get a new message, log it thru c-lightning + scanner := bufio.NewScanner(in) + for { + select { + case <-c.done: + return + default: + if !scanner.Scan() { + if err := scanner.Err(); err != nil { + log.Fatalf( + "can't print out to std err, killing: %v", + err, + ) + } + } + + for _, line := range strings.Split(scanner.Text(), "\n") { + c.log("info", line) + } + } + } + + }(in) +} + func (c *ClnPlugin) log(level string, message string) { params, _ := json.Marshal(&LogNotification{ Level: level, @@ -486,3 +468,17 @@ func scanDoubleNewline( // the buffer if we're at EOF, with no /n/n present return 0, nil, nil } + +// converts a raw cln id to string. The CLN id can either be an integer or a +// string. if it's a string, the quotes are removed. +func idToString(id json.RawMessage) string { + if len(id) == 0 { + return "" + } + + str := string(id) + str = strings.TrimSpace(str) + str = strings.Trim(str, "\"") + str = strings.Trim(str, "'") + return str +} diff --git a/cln_plugin/server.go b/cln_plugin/server.go index 680b83a..e852dab 100644 --- a/cln_plugin/server.go +++ b/cln_plugin/server.go @@ -23,7 +23,7 @@ type subscription struct { type htlcAcceptedMsg struct { id string htlc *HtlcAccepted - timeout <-chan time.Time + timeout time.Time } // Internal htlc result message meant for the recvQueue. @@ -53,10 +53,14 @@ func NewServer(listenAddress string, subscriberTimeout time.Duration) *server { return &server{ listenAddress: listenAddress, subscriberTimeout: subscriberTimeout, - sendQueue: make(chan *htlcAcceptedMsg, 10000), - recvQueue: make(chan *htlcResultMsg, 10000), - started: make(chan struct{}), - startError: make(chan error, 1), + // The send queue exists to buffer messages until a subscriber is active. + sendQueue: make(chan *htlcAcceptedMsg, 10000), + // The receive queue exists mainly to allow returning timeouts to the + // cln plugin. If there is no subscriber active within the subscriber + // timeout period these results can be put directly on the receive queue. + recvQueue: make(chan *htlcResultMsg, 10000), + started: make(chan struct{}), + startError: make(chan error, 1), } } @@ -113,16 +117,20 @@ func (s *server) Stop() { close(s.done) s.grpcServer.Stop() s.grpcServer = nil + log.Printf("Server stopped.") } // Grpc method that is called when a new client subscribes. There can only be // one subscriber active at a time. If there is an error receiving or sending // from or to the subscriber, the subscription is closed. func (s *server) HtlcStream(stream proto.ClnPlugin_HtlcStreamServer) error { - log.Printf("Got HTLC stream subscription request.") s.mtx.Lock() - if s.subscription != nil { + if s.subscription == nil { + log.Printf("Got a new HTLC stream subscription request.") + } else { s.mtx.Unlock() + log.Printf("Got a HTLC stream subscription request, but subscription " + + "was already active.") return fmt.Errorf("already subscribed") } @@ -140,10 +148,17 @@ func (s *server) HtlcStream(stream proto.ClnPlugin_HtlcStreamServer) error { s.mtx.Unlock() defer func() { + // When the HtlcStream function returns, that means the subscriber will + // be gone. Cleanup the subscription so we'll be ready to accept a new + // one later. s.removeSubscriptionIfUnchanged(sb, nil) }() go func() { + // If the context is done, there will be no more connection with the + // client. Listen for context done and clean up the subscriber. + // Cleaning up the subscriber will make the HtlcStream function exit. + // (sb.done or sb.err) <-stream.Context().Done() log.Printf("HtlcStream context is done. Removing subscriber: %v", stream.Context().Err()) s.removeSubscriptionIfUnchanged(sb, stream.Context().Err()) @@ -167,13 +182,14 @@ func (s *server) Send(id string, h *HtlcAccepted) { s.sendQueue <- &htlcAcceptedMsg{ id: id, htlc: h, - timeout: time.After(s.subscriberTimeout), + timeout: time.Now().Add(s.subscriberTimeout), } } // Receives the next htlc resolution message from the grpc client. Returns id // and message. Blocks until a message is available. Returns a nil message if -// the server is done. +// the server is done. This function effectively waits until a subscriber is +// active and has sent a message. func (s *server) Receive() (string, interface{}) { select { case <-s.done: @@ -183,63 +199,6 @@ func (s *server) Receive() (string, interface{}) { } } -// Helper function that blocks until a message from a grpc client is received -// or the server stops. Either returns a received message, or nil if the server -// has stopped. -func (s *server) recv() *proto.HtlcResolution { - for { - // make a copy of the used fields, to make sure state updates don't - // surprise us. The newSubscriber chan is swapped whenever a new - // subscriber arrives. - s.mtx.Lock() - sb := s.subscription - ns := s.newSubscriber - s.mtx.Unlock() - - if sb == nil { - log.Printf("Got no subscribers for receive. Waiting for subscriber.") - select { - case <-s.done: - log.Printf("Done signalled, stopping receive.") - return nil - case <-ns: - log.Printf("New subscription available for receive, continue receive.") - continue - } - } - - // There is a subscription active. Attempt to receive a message. - r, err := sb.stream.Recv() - if err == nil { - log.Printf("Received HtlcResolution %+v", r) - return r - } - - // Receiving the message failed, so the subscription is broken. Remove - // it if it hasn't been updated already. We'll try receiving again in - // the next iteration of the for loop. - log.Printf("Recv() errored, removing subscription: %v", err) - s.removeSubscriptionIfUnchanged(sb, err) - } -} - -// Stops and removes the subscription if this is the currently active -// subscription. If the subscription was changed in the meantime, this function -// does nothing. -func (s *server) removeSubscriptionIfUnchanged(sb *subscription, err error) { - s.mtx.Lock() - // If the subscription reference hasn't changed yet in the meantime, kill it. - if s.subscription == sb { - if err == nil { - close(sb.done) - } else { - sb.err <- err - } - s.subscription = nil - } - s.mtx.Unlock() -} - // Listens to sendQueue for htlc_accepted requests from cln. The message will be // held until a subscriber is active, or the subscriber timeout expires. The // messages are sent to the grpc client in fifo order. @@ -274,7 +233,7 @@ func (s *server) handleHtlcAccepted(msg *htlcAcceptedMsg) { case <-ns: log.Printf("got a new subscriber. continue handleHtlcAccepted.") continue - case <-msg.timeout: + case <-time.After(time.Until(msg.timeout)): log.Printf( "WARNING: htlc with id '%s' timed out after '%v' waiting "+ "for grpc subscriber: %+v", @@ -345,6 +304,67 @@ func (s *server) listenHtlcResponses() { } } +// Helper function that blocks until a message from a grpc client is received +// or the server stops. Either returns a received message, or nil if the server +// has stopped. +func (s *server) recv() *proto.HtlcResolution { + for { + // make a copy of the used fields, to make sure state updates don't + // surprise us. The newSubscriber chan is swapped whenever a new + // subscriber arrives. + s.mtx.Lock() + sb := s.subscription + ns := s.newSubscriber + s.mtx.Unlock() + + if sb == nil { + log.Printf("Got no subscribers for receive. Waiting for subscriber.") + select { + case <-s.done: + log.Printf("Done signalled, stopping receive.") + return nil + case <-ns: + log.Printf("New subscription available for receive, continue receive.") + continue + } + } + + // There is a subscription active. Attempt to receive a message. + r, err := sb.stream.Recv() + if err == nil { + log.Printf("Received HtlcResolution %+v", r) + return r + } + + // Receiving the message failed, so the subscription is broken. Remove + // it if it hasn't been updated already. We'll try receiving again in + // the next iteration of the for loop. + log.Printf("Recv() errored, removing subscription: %v", err) + s.removeSubscriptionIfUnchanged(sb, err) + } +} + +// Stops and removes the subscription if this is the currently active +// subscription. If the subscription was changed in the meantime, this function +// does nothing. +func (s *server) removeSubscriptionIfUnchanged(sb *subscription, err error) { + s.mtx.Lock() + // If the subscription reference hasn't changed yet in the meantime, kill it. + if s.subscription == sb { + if err == nil { + log.Printf("Removing active subscription without error.") + close(sb.done) + } else { + log.Printf("Removing active subscription with error: %v", err) + sb.err <- err + } + s.subscription = nil + } else { + log.Printf("removeSubscriptionIfUnchanged: Subscription already removed.") + } + s.mtx.Unlock() +} + // Maps a grpc result to the corresponding result for cln. The cln message // is a raw json message, so it's easiest to use a map directly. func (s *server) mapResult(outcome interface{}) interface{} { diff --git a/itest/postgres.go b/itest/postgres.go index a8c4f54..5cb5047 100644 --- a/itest/postgres.go +++ b/itest/postgres.go @@ -41,6 +41,7 @@ func NewPostgresContainer(logfile string) (*PostgresContainer, error) { return &PostgresContainer{ password: "pgpassword", port: port, + logfile: logfile, }, nil } @@ -207,9 +208,9 @@ func (c *PostgresContainer) monitorLogs(ctx context.Context) { } defer i.Close() - file, err := os.OpenFile(c.logfile, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0600) + file, err := os.Create(c.logfile) if err != nil { - log.Printf("Could not create container log file: %v", err) + log.Printf("Could not create container log file %v: %v", c.logfile, err) return } defer file.Close() From b77c068e3d9fd25ff35ef352537ddefe0e5f2cc4 Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Tue, 3 Jan 2023 14:43:37 +0100 Subject: [PATCH 131/214] Remove unnecessarily complex go func --- cln_plugin/server.go | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/cln_plugin/server.go b/cln_plugin/server.go index e852dab..e0901a5 100644 --- a/cln_plugin/server.go +++ b/cln_plugin/server.go @@ -154,16 +154,6 @@ func (s *server) HtlcStream(stream proto.ClnPlugin_HtlcStreamServer) error { s.removeSubscriptionIfUnchanged(sb, nil) }() - go func() { - // If the context is done, there will be no more connection with the - // client. Listen for context done and clean up the subscriber. - // Cleaning up the subscriber will make the HtlcStream function exit. - // (sb.done or sb.err) - <-stream.Context().Done() - log.Printf("HtlcStream context is done. Removing subscriber: %v", stream.Context().Err()) - s.removeSubscriptionIfUnchanged(sb, stream.Context().Err()) - }() - select { case <-s.done: log.Printf("HTLC server signalled done. Return EOF.") @@ -174,6 +164,9 @@ func (s *server) HtlcStream(stream proto.ClnPlugin_HtlcStreamServer) error { case err := <-sb.err: log.Printf("HTLC stream signalled error. Return %v", err) return err + case <-stream.Context().Done(): + log.Printf("HtlcStream context is done. Return: %v", stream.Context().Err()) + return stream.Context().Err() } } From 9df0322db45bd27841bc412abb4e0b6ff10ece4d Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Fri, 6 Jan 2023 09:13:41 +0100 Subject: [PATCH 132/214] let grpc server remove subscriber when dropped --- cln_plugin/server.go | 102 ++++++++++++++----------------------------- 1 file changed, 32 insertions(+), 70 deletions(-) diff --git a/cln_plugin/server.go b/cln_plugin/server.go index e0901a5..58cba3f 100644 --- a/cln_plugin/server.go +++ b/cln_plugin/server.go @@ -2,7 +2,6 @@ package cln_plugin import ( "fmt" - "io" "log" "net" "sync" @@ -12,13 +11,6 @@ import ( grpc "google.golang.org/grpc" ) -// A subscription represents a grpc client that is connected to the server. -type subscription struct { - stream proto.ClnPlugin_HtlcStreamServer - done chan struct{} - err chan error -} - // Internal htlc_accepted message meant for the sendQueue. type htlcAcceptedMsg struct { id string @@ -38,11 +30,11 @@ type server struct { subscriberTimeout time.Duration grpcServer *grpc.Server mtx sync.Mutex - subscription *subscription + stream proto.ClnPlugin_HtlcStreamServer newSubscriber chan struct{} started chan struct{} - startError chan error done chan struct{} + startError chan error sendQueue chan *htlcAcceptedMsg recvQueue chan *htlcResultMsg } @@ -114,9 +106,10 @@ func (s *server) Stop() { return } - close(s.done) s.grpcServer.Stop() s.grpcServer = nil + + close(s.done) log.Printf("Server stopped.") } @@ -125,7 +118,7 @@ func (s *server) Stop() { // from or to the subscriber, the subscription is closed. func (s *server) HtlcStream(stream proto.ClnPlugin_HtlcStreamServer) error { s.mtx.Lock() - if s.subscription == nil { + if s.stream == nil { log.Printf("Got a new HTLC stream subscription request.") } else { s.mtx.Unlock() @@ -134,12 +127,7 @@ func (s *server) HtlcStream(stream proto.ClnPlugin_HtlcStreamServer) error { return fmt.Errorf("already subscribed") } - sb := &subscription{ - stream: stream, - done: make(chan struct{}), - err: make(chan error, 1), - } - s.subscription = sb + s.stream = stream // Notify listeners that a new subscriber is active. Replace the chan with // a new one immediately in case this subscriber is dropped later. @@ -147,27 +135,15 @@ func (s *server) HtlcStream(stream proto.ClnPlugin_HtlcStreamServer) error { s.newSubscriber = make(chan struct{}) s.mtx.Unlock() - defer func() { - // When the HtlcStream function returns, that means the subscriber will - // be gone. Cleanup the subscription so we'll be ready to accept a new - // one later. - s.removeSubscriptionIfUnchanged(sb, nil) - }() + <-stream.Context().Done() + log.Printf("HtlcStream context is done. Return: %v", stream.Context().Err()) - select { - case <-s.done: - log.Printf("HTLC server signalled done. Return EOF.") - return io.EOF - case <-sb.done: - log.Printf("HTLC stream signalled done. Return EOF.") - return io.EOF - case err := <-sb.err: - log.Printf("HTLC stream signalled error. Return %v", err) - return err - case <-stream.Context().Done(): - log.Printf("HtlcStream context is done. Return: %v", stream.Context().Err()) - return stream.Context().Err() - } + // Remove the subscriber. + s.mtx.Lock() + s.stream = nil + s.mtx.Unlock() + + return stream.Context().Err() } // Enqueues a htlc_accepted message for send to the grpc client. @@ -212,13 +188,13 @@ func (s *server) listenHtlcRequests() { func (s *server) handleHtlcAccepted(msg *htlcAcceptedMsg) { for { s.mtx.Lock() - sb := s.subscription + stream := s.stream ns := s.newSubscriber s.mtx.Unlock() // If there is no active subscription, wait until there is a new // subscriber, or the message times out. - if sb == nil { + if stream == nil { select { case <-s.done: log.Printf("handleHtlcAccepted received server done. Stop processing.") @@ -234,6 +210,10 @@ func (s *server) handleHtlcAccepted(msg *htlcAcceptedMsg) { s.subscriberTimeout, msg.htlc, ) + + // If the subscriber timeout expires while holding the htlc + // we short circuit the htlc by sending the default result + // (continue) to cln. s.recvQueue <- &htlcResultMsg{ id: msg.id, result: s.defaultResult(), @@ -244,7 +224,7 @@ func (s *server) handleHtlcAccepted(msg *htlcAcceptedMsg) { } // There is a subscriber. Attempt to send the htlc_accepted message. - err := sb.stream.Send(&proto.HtlcAccepted{ + err := stream.Send(&proto.HtlcAccepted{ Correlationid: msg.id, Onion: &proto.Onion{ Payload: msg.htlc.Onion.Payload, @@ -272,9 +252,10 @@ func (s *server) handleHtlcAccepted(msg *htlcAcceptedMsg) { // If we end up here, there was an error sending the message to the // grpc client. - log.Printf("Error sending htlc_accepted message to subscriber. "+ - "Removing subscription: %v", err) - s.removeSubscriptionIfUnchanged(sb, err) + // TODO: If the Send errors, but the context is not done, this will + // currently retry immediately. Check whether the context is really + // done on an error! + log.Printf("Error sending htlc_accepted message to subscriber. Retrying: %v", err) } } @@ -306,11 +287,11 @@ func (s *server) recv() *proto.HtlcResolution { // surprise us. The newSubscriber chan is swapped whenever a new // subscriber arrives. s.mtx.Lock() - sb := s.subscription + stream := s.stream ns := s.newSubscriber s.mtx.Unlock() - if sb == nil { + if stream == nil { log.Printf("Got no subscribers for receive. Waiting for subscriber.") select { case <-s.done: @@ -323,7 +304,7 @@ func (s *server) recv() *proto.HtlcResolution { } // There is a subscription active. Attempt to receive a message. - r, err := sb.stream.Recv() + r, err := stream.Recv() if err == nil { log.Printf("Received HtlcResolution %+v", r) return r @@ -332,32 +313,13 @@ func (s *server) recv() *proto.HtlcResolution { // Receiving the message failed, so the subscription is broken. Remove // it if it hasn't been updated already. We'll try receiving again in // the next iteration of the for loop. - log.Printf("Recv() errored, removing subscription: %v", err) - s.removeSubscriptionIfUnchanged(sb, err) + // TODO: If the Recv errors, but the context is not done, this will + // currently retry immediately. Check whether the context is really + // done on an error! + log.Printf("Recv() errored, Retrying: %v", err) } } -// Stops and removes the subscription if this is the currently active -// subscription. If the subscription was changed in the meantime, this function -// does nothing. -func (s *server) removeSubscriptionIfUnchanged(sb *subscription, err error) { - s.mtx.Lock() - // If the subscription reference hasn't changed yet in the meantime, kill it. - if s.subscription == sb { - if err == nil { - log.Printf("Removing active subscription without error.") - close(sb.done) - } else { - log.Printf("Removing active subscription with error: %v", err) - sb.err <- err - } - s.subscription = nil - } else { - log.Printf("removeSubscriptionIfUnchanged: Subscription already removed.") - } - s.mtx.Unlock() -} - // Maps a grpc result to the corresponding result for cln. The cln message // is a raw json message, so it's easiest to use a map directly. func (s *server) mapResult(outcome interface{}) interface{} { From 2d675fe0dad4d110704db5ba5380e917a676cbd2 Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Fri, 6 Jan 2023 12:25:29 +0100 Subject: [PATCH 133/214] add keepalive settings for cln client/server --- cln_interceptor.go | 11 ++++++++++- cln_plugin/server.go | 11 ++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/cln_interceptor.go b/cln_interceptor.go index 194075c..c3a3cc0 100644 --- a/cln_interceptor.go +++ b/cln_interceptor.go @@ -19,6 +19,7 @@ import ( "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/credentials/insecure" + "google.golang.org/grpc/keepalive" "google.golang.org/grpc/status" ) @@ -45,7 +46,15 @@ func NewClnHtlcInterceptor() *ClnHtlcInterceptor { func (i *ClnHtlcInterceptor) Start() error { ctx, cancel := context.WithCancel(context.Background()) log.Printf("Dialing cln plugin on '%s'", i.pluginAddress) - conn, err := grpc.DialContext(ctx, i.pluginAddress, grpc.WithTransportCredentials(insecure.NewCredentials())) + conn, err := grpc.DialContext( + ctx, + i.pluginAddress, + grpc.WithTransportCredentials(insecure.NewCredentials()), + grpc.WithKeepaliveParams(keepalive.ClientParameters{ + Time: time.Duration(10) * time.Second, + Timeout: time.Duration(10) * time.Second, + }), + ) if err != nil { log.Printf("grpc.Dial error: %v", err) cancel() diff --git a/cln_plugin/server.go b/cln_plugin/server.go index 58cba3f..e11a240 100644 --- a/cln_plugin/server.go +++ b/cln_plugin/server.go @@ -9,6 +9,7 @@ import ( "github.com/breez/lspd/cln_plugin/proto" grpc "google.golang.org/grpc" + "google.golang.org/grpc/keepalive" ) // Internal htlc_accepted message meant for the sendQueue. @@ -76,7 +77,15 @@ func (s *server) Start() error { s.done = make(chan struct{}) s.newSubscriber = make(chan struct{}) - s.grpcServer = grpc.NewServer() + s.grpcServer = grpc.NewServer( + grpc.KeepaliveParams(keepalive.ServerParameters{ + Time: time.Duration(1) * time.Second, + Timeout: time.Duration(10) * time.Second, + }), + grpc.KeepaliveEnforcementPolicy(keepalive.EnforcementPolicy{ + MinTime: time.Duration(1) * time.Second, + }), + ) s.mtx.Unlock() proto.RegisterClnPluginServer(s.grpcServer, s) From 3a34400d951f95d6340b4e276102b81991e6f0d4 Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Fri, 30 Dec 2022 12:44:50 +0100 Subject: [PATCH 134/214] add copyright notice --- cln_plugin/cln_plugin.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cln_plugin/cln_plugin.go b/cln_plugin/cln_plugin.go index 4b217f0..039fc9b 100644 --- a/cln_plugin/cln_plugin.go +++ b/cln_plugin/cln_plugin.go @@ -1,3 +1,7 @@ +// The code in this plugin is highly inspired by and sometimes copied from +// github.com/niftynei/glightning. Therefore pieces of this code are subject +// to Copyright Lisa Neigut (Blockstream) 2019. + package cln_plugin import ( From 94ee938893fbb2be209cef510f5615e3c0262ece Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Tue, 3 Jan 2023 13:03:16 +0100 Subject: [PATCH 135/214] support multiple nodes simultaneously --- cln_client.go | 17 +++- cln_interceptor.go | 24 +++-- config.go | 32 ++++++ db.go | 7 +- htlc_interceptor.go | 2 +- intercept.go | 18 ++-- lnd_client.go | 31 +++--- lnd_interceptor.go | 20 ++-- main.go | 117 +++++++++++++--------- sample.env | 18 +--- server.go | 234 ++++++++++++++++++++++++++++---------------- 11 files changed, 329 insertions(+), 191 deletions(-) create mode 100644 config.go diff --git a/cln_client.go b/cln_client.go index c59b50e..04886ae 100644 --- a/cln_client.go +++ b/cln_client.go @@ -4,7 +4,7 @@ import ( "encoding/hex" "fmt" "log" - "os" + "path/filepath" "github.com/breez/lspd/basetypes" "github.com/btcsuite/btcd/chaincfg/chainhash" @@ -24,15 +24,22 @@ var ( CLOSED_STATUSES = []string{"CLOSED"} ) -func NewClnClient() *ClnClient { - rpcFile := os.Getenv("CLN_SOCKET_NAME") - lightningDir := os.Getenv("CLN_SOCKET_DIR") +func NewClnClient(socketPath string) (*ClnClient, error) { + rpcFile := filepath.Base(socketPath) + if rpcFile == "" || rpcFile == "." { + return nil, fmt.Errorf("invalid socketPath '%s'", socketPath) + } + lightningDir := filepath.Dir(socketPath) + if lightningDir == "" || lightningDir == "." { + return nil, fmt.Errorf("invalid socketPath '%s'", socketPath) + } + client := glightning.NewLightning() client.SetTimeout(60) client.StartUp(rpcFile, lightningDir) return &ClnClient{ client: client, - } + }, nil } func (c *ClnClient) GetInfo() (*GetInfoResult, error) { diff --git a/cln_interceptor.go b/cln_interceptor.go index c3a3cc0..083ce7b 100644 --- a/cln_interceptor.go +++ b/cln_interceptor.go @@ -7,7 +7,6 @@ import ( "fmt" "io" "log" - "os" "sync" "time" @@ -24,6 +23,7 @@ import ( ) type ClnHtlcInterceptor struct { + config *NodeConfig pluginAddress string client *ClnClient pluginClient proto.ClnPluginClient @@ -33,14 +33,23 @@ type ClnHtlcInterceptor struct { cancel context.CancelFunc } -func NewClnHtlcInterceptor() *ClnHtlcInterceptor { +func NewClnHtlcInterceptor(conf *NodeConfig) (*ClnHtlcInterceptor, error) { + if conf.Cln == nil { + return nil, fmt.Errorf("missing cln config") + } + + client, err := NewClnClient(conf.Cln.SocketPath) + if err != nil { + return nil, err + } i := &ClnHtlcInterceptor{ - pluginAddress: os.Getenv("CLN_PLUGIN_ADDRESS"), - client: NewClnClient(), + config: conf, + pluginAddress: conf.Cln.PluginAddress, + client: client, } i.initWg.Add(1) - return i + return i, nil } func (i *ClnHtlcInterceptor) Start() error { @@ -135,7 +144,7 @@ func (i *ClnHtlcInterceptor) intercept() error { interceptorClient.Send(i.defaultResolution(request)) i.doneWg.Done() } - interceptResult := intercept(paymentHash, request.Onion.ForwardMsat, request.Htlc.CltvExpiry) + interceptResult := intercept(i.client, i.config, paymentHash, request.Onion.ForwardMsat, request.Htlc.CltvExpiry) switch interceptResult.action { case INTERCEPT_RESUME_WITH_ONION: interceptorClient.Send(i.resumeWithOnion(request, interceptResult)) @@ -165,9 +174,8 @@ func (i *ClnHtlcInterceptor) Stop() error { return nil } -func (i *ClnHtlcInterceptor) WaitStarted() LightningClient { +func (i *ClnHtlcInterceptor) WaitStarted() { i.initWg.Wait() - return i.client } func (i *ClnHtlcInterceptor) resumeWithOnion(request *proto.HtlcAccepted, interceptResult interceptResult) *proto.HtlcResolution { diff --git a/config.go b/config.go new file mode 100644 index 0000000..a367682 --- /dev/null +++ b/config.go @@ -0,0 +1,32 @@ +package main + +type NodeConfig struct { + LspdPrivateKey string `json:"lspdPrivateKey"` + Token string `json:"token"` + Host string `json:"host"` + PublicChannelAmount int64 `json:"publicChannelAmount,string"` + ChannelAmount uint64 `json:"channelAmount,string"` + ChannelPrivate bool `json:"channelPrivate"` + TargetConf uint32 `json:"targetConf,string"` + MinHtlcMsat uint64 `json:"minHtlcMsat,string"` + BaseFeeMsat uint64 `json:"baseFeeMsat,string"` + FeeRate float64 `json:"feeRate,string"` + TimeLockDelta uint32 `json:"timeLockDelta,string"` + ChannelFeePermyriad int64 `json:"channelFeePermyriad,string"` + ChannelMinimumFeeMsat int64 `json:"channelMinimumFeeMsat,string"` + AdditionalChannelCapacity int64 `json:"additionalChannelCapacity,string"` + MaxInactiveDuration uint64 `json:"maxInactiveDuration,string"` + Lnd *LndConfig `json:"lnd,omitempty"` + Cln *ClnConfig `json:"cln,omitempty"` +} + +type LndConfig struct { + Address string `json:"address"` + Cert string `json:"cert"` + Macaroon string `json:"macaroon"` +} + +type ClnConfig struct { + PluginAddress string `json:"pluginAddress"` + SocketPath string `json:"socketPath"` +} diff --git a/db.go b/db.go index b68cbf5..f982d69 100644 --- a/db.go +++ b/db.go @@ -5,7 +5,6 @@ import ( "encoding/hex" "fmt" "log" - "os" "time" "github.com/btcsuite/btcd/wire" @@ -18,11 +17,11 @@ var ( pgxPool *pgxpool.Pool ) -func pgConnect() error { +func pgConnect(databaseUrl string) error { var err error - pgxPool, err = pgxpool.Connect(context.Background(), os.Getenv("DATABASE_URL")) + pgxPool, err = pgxpool.Connect(context.Background(), databaseUrl) if err != nil { - return fmt.Errorf("pgxpool.Connect(%v): %w", os.Getenv("DATABASE_URL"), err) + return fmt.Errorf("pgxpool.Connect(%v): %w", databaseUrl, err) } return nil } diff --git a/htlc_interceptor.go b/htlc_interceptor.go index 356463b..e572f0d 100644 --- a/htlc_interceptor.go +++ b/htlc_interceptor.go @@ -3,5 +3,5 @@ package main type HtlcInterceptor interface { Start() error Stop() error - WaitStarted() LightningClient + WaitStarted() } diff --git a/intercept.go b/intercept.go index 81ffea2..d15c861 100644 --- a/intercept.go +++ b/intercept.go @@ -45,7 +45,7 @@ type interceptResult struct { onionBlob []byte } -func intercept(reqPaymentHash []byte, reqOutgoingAmountMsat uint64, reqOutgoingExpiry uint32) interceptResult { +func intercept(client LightningClient, config *NodeConfig, reqPaymentHash []byte, reqOutgoingAmountMsat uint64, reqOutgoingExpiry uint32) interceptResult { reqPaymentHashStr := hex.EncodeToString(reqPaymentHash) resp, _, _ := payHashGroup.Do(reqPaymentHashStr, func() (interface{}, error) { paymentHash, paymentSecret, destination, incomingAmountMsat, outgoingAmountMsat, channelPoint, err := paymentInfo(reqPaymentHash) @@ -66,7 +66,7 @@ func intercept(reqPaymentHash []byte, reqOutgoingAmountMsat uint64, reqOutgoingE if channelPoint == nil { if bytes.Equal(paymentHash, reqPaymentHash) { - channelPoint, err = openChannel(client, reqPaymentHash, destination, incomingAmountMsat) + channelPoint, err = openChannel(client, config, reqPaymentHash, destination, incomingAmountMsat) if err != nil { log.Printf("openChannel(%x, %v) err: %v", destination, incomingAmountMsat, err) return interceptResult{ @@ -213,10 +213,10 @@ func intercept(reqPaymentHash []byte, reqOutgoingAmountMsat uint64, reqOutgoingE return resp.(interceptResult) } -func checkPayment(incomingAmountMsat, outgoingAmountMsat int64) error { - fees := incomingAmountMsat * channelFeePermyriad / 10_000 / 1_000 * 1_000 - if fees < channelMinimumFeeMsat { - fees = channelMinimumFeeMsat +func checkPayment(config *NodeConfig, incomingAmountMsat, outgoingAmountMsat int64) error { + fees := incomingAmountMsat * config.ChannelFeePermyriad / 10_000 / 1_000 * 1_000 + if fees < config.ChannelMinimumFeeMsat { + fees = config.ChannelMinimumFeeMsat } if incomingAmountMsat-outgoingAmountMsat < fees { return fmt.Errorf("not enough fees") @@ -224,9 +224,9 @@ func checkPayment(incomingAmountMsat, outgoingAmountMsat int64) error { return nil } -func openChannel(client LightningClient, paymentHash, destination []byte, incomingAmountMsat int64) (*wire.OutPoint, error) { - capacity := incomingAmountMsat/1000 + additionalChannelCapacity - if capacity == publicChannelAmount { +func openChannel(client LightningClient, config *NodeConfig, paymentHash, destination []byte, incomingAmountMsat int64) (*wire.OutPoint, error) { + capacity := incomingAmountMsat/1000 + config.AdditionalChannelCapacity + if capacity == config.PublicChannelAmount { capacity++ } channelPoint, err := client.OpenChannel(&OpenChannelRequest{ diff --git a/lnd_client.go b/lnd_client.go index f98054c..8bfe813 100644 --- a/lnd_client.go +++ b/lnd_client.go @@ -3,11 +3,10 @@ package main import ( "context" "crypto/x509" + "encoding/base64" "encoding/hex" "fmt" "log" - "os" - "strings" "github.com/breez/lspd/basetypes" "github.com/btcsuite/btcd/wire" @@ -17,7 +16,6 @@ import ( "github.com/lightningnetwork/lnd/lnrpc/routerrpc" "google.golang.org/grpc" "google.golang.org/grpc/credentials" - "google.golang.org/grpc/metadata" ) type LndClient struct { @@ -27,18 +25,28 @@ type LndClient struct { conn *grpc.ClientConn } -func NewLndClient() *LndClient { +func NewLndClient(conf *LndConfig) (*LndClient, error) { + cert, err := base64.StdEncoding.DecodeString(conf.Cert) + if err != nil { + return nil, fmt.Errorf("failed to decode cert: %w", err) + } + + _, 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(strings.Replace(os.Getenv("LND_CERT"), "\\n", "\n", -1))) { - log.Fatalf("credentials: failed to append certificates") + if !cp.AppendCertsFromPEM(cert) { + return nil, fmt.Errorf("credentials: failed to append certificates") } creds := credentials.NewClientTLSFromCert(cp, "") - macCred := NewMacaroonCredential(os.Getenv("LND_MACAROON_HEX")) + macCred := NewMacaroonCredential(conf.Macaroon) // Address of an LND instance conn, err := grpc.Dial( - os.Getenv("LND_ADDRESS"), + conf.Address, grpc.WithTransportCredentials(creds), grpc.WithPerRPCCredentials(macCred), ) @@ -54,7 +62,7 @@ func NewLndClient() *LndClient { routerClient: routerClient, chainNotifierClient: chainNotifierClient, conn: conn, - } + }, nil } func (c *LndClient) Close() { @@ -152,13 +160,12 @@ func (c *LndClient) GetChannel(peerID []byte, channelPoint wire.OutPoint) (*GetC func (c *LndClient) GetNodeChannelCount(nodeID []byte) (int, error) { nodeIDStr := hex.EncodeToString(nodeID) - clientCtx := metadata.AppendToOutgoingContext(context.Background(), "macaroon", os.Getenv("LND_MACAROON_HEX")) - listResponse, err := c.client.ListChannels(clientCtx, &lnrpc.ListChannelsRequest{}) + listResponse, err := c.client.ListChannels(context.Background(), &lnrpc.ListChannelsRequest{}) if err != nil { return 0, err } - pendingResponse, err := c.client.PendingChannels(clientCtx, &lnrpc.PendingChannelsRequest{}) + pendingResponse, err := c.client.PendingChannels(context.Background(), &lnrpc.PendingChannelsRequest{}) if err != nil { return 0, err } diff --git a/lnd_interceptor.go b/lnd_interceptor.go index a3775c4..3cc152c 100644 --- a/lnd_interceptor.go +++ b/lnd_interceptor.go @@ -14,6 +14,7 @@ import ( ) type LndHtlcInterceptor struct { + config *NodeConfig client *LndClient initWg sync.WaitGroup doneWg sync.WaitGroup @@ -21,14 +22,22 @@ type LndHtlcInterceptor struct { cancel context.CancelFunc } -func NewLndHtlcInterceptor() *LndHtlcInterceptor { +func NewLndHtlcInterceptor(conf *NodeConfig) (*LndHtlcInterceptor, error) { + if conf.Lnd == nil { + return nil, fmt.Errorf("missing lnd configuration") + } + client, err := NewLndClient(conf.Lnd) + if err != nil { + return nil, err + } i := &LndHtlcInterceptor{ - client: NewLndClient(), + config: conf, + client: client, } i.initWg.Add(1) - return i + return i, nil } func (i *LndHtlcInterceptor) Start() error { @@ -47,9 +56,8 @@ func (i *LndHtlcInterceptor) Stop() error { return nil } -func (i *LndHtlcInterceptor) WaitStarted() LightningClient { +func (i *LndHtlcInterceptor) WaitStarted() { i.initWg.Wait() - return i.client } func (i *LndHtlcInterceptor) intercept() error { @@ -113,7 +121,7 @@ func (i *LndHtlcInterceptor) intercept() error { i.doneWg.Add(1) go func() { - interceptResult := intercept(request.PaymentHash, request.OutgoingAmountMsat, request.OutgoingExpiry) + interceptResult := intercept(i.client, i.config, request.PaymentHash, request.OutgoingAmountMsat, request.OutgoingExpiry) switch interceptResult.action { case INTERCEPT_RESUME_WITH_ONION: interceptorClient.Send(&routerrpc.ForwardHtlcInterceptResponse{ diff --git a/main.go b/main.go index 208a6a2..f28ea75 100644 --- a/main.go +++ b/main.go @@ -1,6 +1,7 @@ package main import ( + "encoding/json" "fmt" "log" "os" @@ -21,59 +22,79 @@ func main() { return } - err := pgConnect() + n := os.Getenv("NODES") + var nodes []*NodeConfig + err := json.Unmarshal([]byte(n), &nodes) + if err != nil { + log.Fatalf("failed to unmarshal NODES env: %v", err) + } + + if len(nodes) == 0 { + log.Fatalf("need at least one node configured in NODES.") + } + + var interceptors []HtlcInterceptor + for _, node := range nodes { + var interceptor HtlcInterceptor + if node.Lnd != nil { + interceptor, err = NewLndHtlcInterceptor(node) + if err != nil { + log.Fatalf("failed to initialize LND interceptor: %v", err) + } + } + + if node.Cln != nil { + interceptor, err = NewClnHtlcInterceptor(node) + if err != nil { + log.Fatalf("failed to initialize CLN interceptor: %v", err) + } + } + + if interceptor == nil { + log.Fatalf("node has to be either cln or lnd") + } + + interceptors = append(interceptors, interceptor) + } + + address := os.Getenv("LISTEN_ADDRESS") + certMagicDomain := os.Getenv("CERTMAGIC_DOMAIN") + s, err := NewGrpcServer(nodes, address, certMagicDomain) + if err != nil { + log.Fatalf("failed to initialize grpc server: %v", err) + } + + databaseUrl := os.Getenv("DATABASE_URL") + err = pgConnect(databaseUrl) if err != nil { log.Fatalf("pgConnect() error: %v", err) } - runCln := os.Getenv("RUN_CLN") == "true" - runLnd := os.Getenv("RUN_LND") == "true" - - if runCln && runLnd { - log.Fatalf("One of RUN_CLN or RUN_LND must be true, not both.") - } - - if !runCln && !runLnd { - log.Fatalf("Either RUN_CLN or RUN_LND must be true.") - } - - var interceptor HtlcInterceptor - if runCln { - interceptor = NewClnHtlcInterceptor() - } - - if runLnd { - interceptor = NewLndHtlcInterceptor() - } - - s := NewGrpcServer() - var wg sync.WaitGroup - wg.Add(2) + wg.Add(len(interceptors) + 1) - go func() { - err := interceptor.Start() - if err == nil { - log.Printf("Interceptor stopped.") - } else { - log.Printf("FATAL. Interceptor stopped with error: %v", err) + stopInterceptors := func() { + for _, interceptor := range interceptors { + interceptor.Stop() } - s.Stop() - wg.Done() - }() - - client = interceptor.WaitStarted() - info, err := client.GetInfo() - if err != nil { - log.Fatalf("client.GetInfo() error: %v", err) } - log.Printf("Connected to node '%s', alias '%s'", info.Pubkey, info.Alias) - if nodeName == "" { - nodeName = info.Alias - } - if nodePubkey == "" { - nodePubkey = info.Pubkey + for _, interceptor := range interceptors { + i := interceptor + go func() { + err := i.Start() + if err == nil { + log.Printf("Interceptor stopped.") + } else { + log.Printf("FATAL. Interceptor stopped with error: %v", err) + } + + wg.Done() + + // If any interceptor stops, stop everything, so we're able to restart using systemd. + s.Stop() + stopInterceptors() + }() } go func() { @@ -84,8 +105,10 @@ func main() { log.Printf("FATAL. GRPC server stopped with error: %v", err) } - interceptor.Stop() wg.Done() + + // If the server stops, stop everything else, so we're able to restart using systemd. + stopInterceptors() }() c := make(chan os.Signal, 1) @@ -93,8 +116,10 @@ func main() { go func() { sig := <-c log.Printf("Received stop signal %v. Stopping.", sig) + + // Stop everything gracefully on stop signal s.Stop() - interceptor.Stop() + stopInterceptors() }() wg.Wait() diff --git a/sample.env b/sample.env index 3dfb15f..407136a 100644 --- a/sample.env +++ b/sample.env @@ -3,12 +3,6 @@ LISTEN_ADDRESS= ### a certificate from Let's Encrypt #CERTMAGIC_DOMAIN= -NODE_NAME= -NODE_PUBKEY= -NODE_HOST= - -TOKEN= -LSPD_PRIVATE_KEY= DATABASE_URL= AWS_REGION= @@ -23,14 +17,4 @@ CHANNELMISMATCH_NOTIFICATION_TO='["Name1 "]' CHANNELMISMATCH_NOTIFICATION_CC='["Name2 ","Name3 "]' CHANNELMISMATCH_NOTIFICATION_FROM="Name4 " -# LND specific environment variables -LND_ADDRESS= -LND_CERT= #replace each eol by \\n -LND_MACAROON_HEX= -RUN_LND=true - -# CLN specific environment variables -CLN_PLUGIN_ADDRESS=
-CLN_SOCKET_DIR= -CLN_SOCKET_NAME= -RUN_CLN=true +NODES='[ { "lspdPrivateKey": "", "token": "", "host": "", "publicChannelAmount": "1000183", "channelAmount": "100000", "channelPrivate": false, "targetConf": "6", "minHtlcMsat": "600", "baseFeeMsat": "1000", "feeRate": "0.000001", "timeLockDelta": "144", "channelFeePermyriad": "40", "channelMinimumFeeMsat": "2000000", "additionalChannelCapacity": "100000", "maxInactiveDuration": "3888000", "lnd": { "address": "", "cert": "", "macaroon": "" } }, { "lspdPrivateKey": "", "token": "", "host": "", "publicChannelAmount": "1000183", "channelAmount": "100000", "channelPrivate": false, "targetConf": "6", "minHtlcMsat": "600", "baseFeeMsat": "1000", "feeRate": "0.000001", "timeLockDelta": "144", "channelFeePermyriad": "40", "channelMinimumFeeMsat": "2000000", "additionalChannelCapacity": "100000", "maxInactiveDuration": "3888000", "cln": { "pluginAddress": "
", "socketPath": "" } } ]' diff --git a/server.go b/server.go index 03aaba5..cfae080 100644 --- a/server.go +++ b/server.go @@ -7,14 +7,14 @@ import ( "fmt" "log" "net" - "os" - "strconv" + "strings" "github.com/breez/lspd/btceclegacy" lspdrpc "github.com/breez/lspd/rpc" ecies "github.com/ecies/go/v2" "github.com/golang/protobuf/proto" 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" @@ -25,61 +25,61 @@ import ( "github.com/btcsuite/btcd/wire" "github.com/caddyserver/certmagic" "github.com/lightningnetwork/lnd/lnwire" - "golang.org/x/sync/singleflight" -) - -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 { - lis net.Listener - s *grpc.Server + address string + certmagicDomain string + lis net.Listener + s *grpc.Server + nodes map[string]*node } -var ( +type node struct { client LightningClient - openChannelReqGroup singleflight.Group + nodeName string + nodePubkey string + nodeConfig *NodeConfig privateKey *btcec.PrivateKey publicKey *btcec.PublicKey eciesPrivateKey *ecies.PrivateKey eciesPublicKey *ecies.PublicKey - nodeName = os.Getenv("NODE_NAME") - nodePubkey = os.Getenv("NODE_PUBKEY") -) + openChannelReqGroup singleflight.Group +} func (s *server) ChannelInformation(ctx context.Context, in *lspdrpc.ChannelInformationRequest) (*lspdrpc.ChannelInformationReply, error) { + node, err := getNode(ctx) + if err != nil { + return nil, err + } + 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, + Name: node.nodeName, + Pubkey: node.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), }, nil } func (s *server) RegisterPayment(ctx context.Context, in *lspdrpc.RegisterPaymentRequest) (*lspdrpc.RegisterPaymentReply, error) { - data, err := ecies.Decrypt(eciesPrivateKey, in.Blob) + node, err := getNode(ctx) + if err != nil { + return nil, err + } + + 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(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) @@ -94,7 +94,7 @@ func (s *server) RegisterPayment(ctx context.Context, in *lspdrpc.RegisterPaymen } 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) + err = checkPayment(node.nodeConfig, 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) @@ -108,36 +108,30 @@ func (s *server) RegisterPayment(ctx context.Context, in *lspdrpc.RegisterPaymen } func (s *server) OpenChannel(ctx context.Context, in *lspdrpc.OpenChannelRequest) (*lspdrpc.OpenChannelReply, error) { - r, err, _ := openChannelReqGroup.Do(in.Pubkey, func() (interface{}, error) { + node, err := getNode(ctx) + if err != nil { + return nil, err + } + + r, err, _ := node.openChannelReqGroup.Do(in.Pubkey, func() (interface{}, error) { pubkey, err := hex.DecodeString(in.Pubkey) if err != nil { return nil, err } - channelCount, err := client.GetNodeChannelCount(pubkey) + channelCount, err := node.client.GetNodeChannelCount(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 outPoint *wire.OutPoint if channelCount == 0 { - outPoint, err = client.OpenChannel(&OpenChannelRequest{ - CapacitySat: uint64(channelAmount), + outPoint, err = node.client.OpenChannel(&OpenChannelRequest{ + CapacitySat: node.nodeConfig.ChannelAmount, Destination: pubkey, - TargetConf: targetConf, - MinHtlcMsat: minHtlcMsat, - IsPrivate: isPrivate, + TargetConf: node.nodeConfig.TargetConf, + MinHtlcMsat: node.nodeConfig.MinHtlcMsat, + IsPrivate: node.nodeConfig.ChannelPrivate, }) if err != nil { @@ -157,13 +151,13 @@ func (s *server) OpenChannel(ctx context.Context, in *lspdrpc.OpenChannelRequest return r.(*lspdrpc.OpenChannelReply), err } -func getSignedEncryptedData(in *lspdrpc.Encrypted) (string, []byte, bool, error) { +func (n *node) getSignedEncryptedData(in *lspdrpc.Encrypted) (string, []byte, bool, error) { usedEcies := true - signedBlob, err := ecies.Decrypt(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(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) @@ -198,7 +192,12 @@ func getSignedEncryptedData(in *lspdrpc.Encrypted) (string, []byte, bool, error) } func (s *server) CheckChannels(ctx context.Context, in *lspdrpc.Encrypted) (*lspdrpc.Encrypted, error) { - nodeID, data, usedEcies, err := getSignedEncryptedData(in) + node, err := getNode(ctx) + if err != nil { + return nil, err + } + + nodeID, data, usedEcies, err := node.getSignedEncryptedData(in) if err != nil { log.Printf("getSignedEncryptedData error: %v", err) return nil, fmt.Errorf("getSignedEncryptedData error: %v", err) @@ -214,7 +213,7 @@ func (s *server) CheckChannels(ctx context.Context, in *lspdrpc.Encrypted) (*lsp log.Printf("getNotFakeChannels(%v) error: %v", checkChannelsRequest.FakeChannels, err) return nil, fmt.Errorf("getNotFakeChannels(%v) error: %w", checkChannelsRequest.FakeChannels, err) } - closedChannels, err := 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) @@ -236,7 +235,7 @@ func (s *server) CheckChannels(ctx context.Context, in *lspdrpc.Encrypted) (*lsp var encrypted []byte if usedEcies { - encrypted, err = ecies.Encrypt(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) @@ -269,35 +268,82 @@ func getNotFakeChannels(nodeID string, channelPoints map[string]uint64) (map[str return r, nil } -func NewGrpcServer() *server { - return &server{} +func NewGrpcServer(configs []*NodeConfig, address string, certmagicDomain string) (*server, 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) + + // TODO: Set nodename & nodepubkey + 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 = NewLndClient(config.Lnd) + if err != nil { + return nil, err + } + } + + if config.Cln != nil { + node.client, err = NewClnClient(config.Cln.SocketPath) + if err != nil { + return nil, err + } + } + + _, exists := nodes[config.Token] + if exists { + return nil, fmt.Errorf("cannot have multiple nodes with the same token") + } + + nodes[config.Token] = node + } + + return &server{ + address: address, + certmagicDomain: certmagicDomain, + nodes: nodes, + }, nil } func (s *server) Start() error { - pk, 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) - } - - eciesPrivateKey = ecies.NewPrivateKeyFromBytes(pk) - eciesPublicKey = eciesPrivateKey.PublicKey - privateKey, publicKey = btcec.PrivKeyFromBytes(pk) - - certmagicDomain := os.Getenv("CERTMAGIC_DOMAIN") - address := os.Getenv("LISTEN_ADDRESS") var lis net.Listener - if certmagicDomain == "" { + if s.certmagicDomain == "" { var err error - lis, err = net.Listen("tcp", address) + lis, err = net.Listen("tcp", s.address) if err != nil { log.Fatalf("failed to listen: %v", err) } } else { - tlsConfig, err := certmagic.TLS([]string{certmagicDomain}) + tlsConfig, err := certmagic.TLS([]string{s.certmagicDomain}) if err != nil { log.Fatalf("failed to run certmagic: %v", err) } - lis, err = tls.Listen("tcp", address, tlsConfig) + lis, err = tls.Listen("tcp", s.address, tlsConfig) if err != nil { log.Fatalf("failed to listen: %v", err) } @@ -307,9 +353,17 @@ func (s *server) Start() error { 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) + if !strings.HasPrefix(auth, "Bearer ") { + continue } + + token := strings.Replace(auth, "Bearer ", "", 1) + node, ok := s.nodes[token] + if !ok { + continue + } + + return handler(context.WithValue(ctx, "node", node), req) } } return nil, status.Errorf(codes.PermissionDenied, "Not authorized") @@ -332,3 +386,17 @@ func (s *server) Stop() { srv.GracefulStop() } } + +func getNode(ctx context.Context) (*node, error) { + n := ctx.Value("node") + if n == nil { + return nil, status.Errorf(codes.PermissionDenied, "Not authorized") + } + + node, ok := n.(*node) + if !ok || node == nil { + return nil, status.Errorf(codes.PermissionDenied, "Not authorized") + } + + return node, nil +} From 5c7f22b2f2aeed27fb180f027309c7cb919ee28b Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Tue, 3 Jan 2023 14:29:45 +0100 Subject: [PATCH 136/214] update tests to use new lspd startup --- itest/cln_lspd_node.go | 10 +++++----- itest/lnd_lspd_node.go | 28 ++++++++++++++++------------ itest/lspd_node.go | 26 ++++++++++++++++++++------ 3 files changed, 41 insertions(+), 23 deletions(-) diff --git a/itest/cln_lspd_node.go b/itest/cln_lspd_node.go index c8731d3..4080523 100644 --- a/itest/cln_lspd_node.go +++ b/itest/cln_lspd_node.go @@ -59,12 +59,12 @@ func NewClnLspdNode(h *lntest.TestHarness, m *lntest.Miner, name string) LspNode "--dev-allowdustreserve=true", } lightningNode := lntest.NewClnNode(h, m, name, args...) - lspbase, err := newLspd(h, name, - "RUN_CLN=true", - fmt.Sprintf("CLN_PLUGIN_ADDRESS=%s", pluginAddress), - fmt.Sprintf("CLN_SOCKET_DIR=%s", lightningNode.SocketDir()), - fmt.Sprintf("CLN_SOCKET_NAME=%s", lightningNode.SocketFile()), + cln := fmt.Sprintf( + `{ "pluginAddress": "%s", "socketPath": "%s" }`, + pluginAddress, + filepath.Join(lightningNode.SocketDir(), lightningNode.SocketFile()), ) + lspbase, err := newLspd(h, name, nil, &cln) if err != nil { h.T.Fatalf("failed to initialize lspd") } diff --git a/itest/lnd_lspd_node.go b/itest/lnd_lspd_node.go index e096361..01115c0 100644 --- a/itest/lnd_lspd_node.go +++ b/itest/lnd_lspd_node.go @@ -1,6 +1,7 @@ package itest import ( + "encoding/base64" "fmt" "log" "os" @@ -47,13 +48,13 @@ func NewLndLspdNode(h *lntest.TestHarness, m *lntest.Miner, name string) LspNode } lightningNode := lntest.NewLndNode(h, m, name, args...) - tlsCert := strings.Replace(string(lightningNode.TlsCert()), "\n", "\\n", -1) - lspBase, err := newLspd(h, name, - "RUN_LND=true", - fmt.Sprintf("LND_CERT=\"%s\"", tlsCert), - fmt.Sprintf("LND_ADDRESS=%s", lightningNode.GrpcHost()), - fmt.Sprintf("LND_MACAROON_HEX=%x", lightningNode.Macaroon()), + lnd := fmt.Sprintf( + `{ "address": "%s", "cert": "%s", "macaroon": "%x" }`, + lightningNode.GrpcHost(), + base64.StdEncoding.EncodeToString(lightningNode.TlsCert()), + lightningNode.Macaroon(), ) + lspBase, err := newLspd(h, name, &lnd, nil) if err != nil { h.T.Fatalf("failed to initialize lspd") } @@ -111,13 +112,16 @@ func (c *LndLspNode) Start() { split := strings.Split(string(scriptFile), "\n") for i, s := range split { - if strings.HasPrefix(s, "export LND_CERT") { - tlsCert := strings.Replace(string(c.lightningNode.TlsCert()), "\n", "\\n", -1) - split[i] = fmt.Sprintf("export LND_CERT=\"%s\"", tlsCert) - } + if strings.HasPrefix(s, "export NODES") { + ext := fmt.Sprintf( + `"lnd": { "address": "%s", "cert": "%s", "macaroon": "%x" }}]'`, + c.lightningNode.GrpcHost(), + base64.StdEncoding.EncodeToString(c.lightningNode.TlsCert()), + c.lightningNode.Macaroon(), + ) + start, _, _ := strings.Cut(s, `"lnd"`) - if strings.HasPrefix(s, "export LND_MACAROON_HEX") { - split[i] = fmt.Sprintf("export LND_MACAROON_HEX=%x", c.lightningNode.Macaroon()) + split[i] = start + ext } } newContent := strings.Join(split, "\n") diff --git a/itest/lspd_node.go b/itest/lspd_node.go index c4601bc..16ac1a2 100644 --- a/itest/lspd_node.go +++ b/itest/lspd_node.go @@ -57,7 +57,7 @@ type lspBase struct { postgresBackend *PostgresContainer } -func newLspd(h *lntest.TestHarness, name string, envExt ...string) (*lspBase, error) { +func newLspd(h *lntest.TestHarness, name string, lnd *string, cln *string, envExt ...string) (*lspBase, error) { scriptDir := h.GetDirectory(fmt.Sprintf("lspd-%s", name)) log.Printf("%s: Creating LSPD in dir %s", name, scriptDir) @@ -87,14 +87,28 @@ func newLspd(h *lntest.TestHarness, name string, envExt ...string) (*lspBase, er eciesPubl := ecies.NewPrivateKeyFromBytes(lspdPrivateKeyBytes).PublicKey host := "localhost" grpcAddress := fmt.Sprintf("%s:%d", host, lspdPort) + var ext string + if lnd != nil { + ext = fmt.Sprintf(`"lnd": %s`, *lnd) + } else if cln != nil { + ext = fmt.Sprintf(`"cln": %s`, *cln) + } else { + h.T.Fatalf("%s: need either lnd or cln config", name) + } + + nodes := fmt.Sprintf( + `NODES='[ { "lspdPrivateKey": "%x", "token": "hello", "host": "host:port",`+ + ` "publicChannelAmount": "1000183", "channelAmount": "100000", "channelPrivate": false,`+ + ` "targetConf": "6", "minHtlcMsat": "600", "baseFeeMsat": "1000", "feeRate": "0.000001",`+ + ` "timeLockDelta": "144", "channelFeePermyriad": "40", "channelMinimumFeeMsat": "2000000",`+ + ` "additionalChannelCapacity": "100000", "maxInactiveDuration": "3888000", %s}]'`, + lspdPrivateKeyBytes, + ext, + ) env := []string{ - "NODE_NAME=lsp", - "NODE_PUBKEY=dunno", - "NODE_HOST=host:port", - "TOKEN=hello", + nodes, fmt.Sprintf("DATABASE_URL=%s", postgresBackend.ConnectionString()), fmt.Sprintf("LISTEN_ADDRESS=%s", grpcAddress), - fmt.Sprintf("LSPD_PRIVATE_KEY=%x", lspdPrivateKeyBytes), } env = append(env, envExt...) From 6e20a558506a3e7069dcba23607ac41c9ee1e536 Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Mon, 9 Jan 2023 10:29:08 +0100 Subject: [PATCH 137/214] populate name and pubkey from config or node --- config.go | 2 ++ itest/config_test.go | 39 +++++++++++++++++++++++++++++++++++++++ itest/lspd_node.go | 3 ++- sample.env | 2 +- server.go | 24 +++++++++++++++++++----- 5 files changed, 63 insertions(+), 7 deletions(-) create mode 100644 itest/config_test.go diff --git a/config.go b/config.go index a367682..af9261b 100644 --- a/config.go +++ b/config.go @@ -1,6 +1,8 @@ package main type NodeConfig struct { + Name string `json:name,omitempty` + NodePubkey string `json:nodePubkey,omitempty` LspdPrivateKey string `json:"lspdPrivateKey"` Token string `json:"token"` Host string `json:"host"` diff --git a/itest/config_test.go b/itest/config_test.go new file mode 100644 index 0000000..8134902 --- /dev/null +++ b/itest/config_test.go @@ -0,0 +1,39 @@ +package itest + +import ( + "encoding/hex" + "log" + "testing" + "time" + + "github.com/breez/lntest" + lspd "github.com/breez/lspd/rpc" + "github.com/stretchr/testify/assert" +) + +func TestConfigParameters(t *testing.T) { + deadline, _ := t.Deadline() + h := lntest.NewTestHarness(t, deadline) + defer h.TearDown() + + m := lntest.NewMiner(h) + m.Start() + + lsp := NewClnLspdNode(h, m, "lsp") + lsp.Start() + + log.Printf("Waiting %v to allow lsp server to activate.", htlcInterceptorDelay) + <-time.After(htlcInterceptorDelay) + + info, err := lsp.Rpc().ChannelInformation( + h.Ctx, + &lspd.ChannelInformationRequest{}, + ) + + if err != nil { + t.Fatalf("failed to get channelinformation: %v", err) + } + + assert.Equal(t, hex.EncodeToString(lsp.LightningNode().NodeId()), info.Pubkey) + assert.Equal(t, "lsp", info.Name) +} diff --git a/itest/lspd_node.go b/itest/lspd_node.go index 16ac1a2..df1fc15 100644 --- a/itest/lspd_node.go +++ b/itest/lspd_node.go @@ -97,11 +97,12 @@ func newLspd(h *lntest.TestHarness, name string, lnd *string, cln *string, envEx } nodes := fmt.Sprintf( - `NODES='[ { "lspdPrivateKey": "%x", "token": "hello", "host": "host:port",`+ + `NODES='[ { "name": "%s", "lspdPrivateKey": "%x", "token": "hello", "host": "host:port",`+ ` "publicChannelAmount": "1000183", "channelAmount": "100000", "channelPrivate": false,`+ ` "targetConf": "6", "minHtlcMsat": "600", "baseFeeMsat": "1000", "feeRate": "0.000001",`+ ` "timeLockDelta": "144", "channelFeePermyriad": "40", "channelMinimumFeeMsat": "2000000",`+ ` "additionalChannelCapacity": "100000", "maxInactiveDuration": "3888000", %s}]'`, + name, lspdPrivateKeyBytes, ext, ) diff --git a/sample.env b/sample.env index 407136a..1057b32 100644 --- a/sample.env +++ b/sample.env @@ -17,4 +17,4 @@ CHANNELMISMATCH_NOTIFICATION_TO='["Name1 "]' CHANNELMISMATCH_NOTIFICATION_CC='["Name2 ","Name3 "]' CHANNELMISMATCH_NOTIFICATION_FROM="Name4 " -NODES='[ { "lspdPrivateKey": "", "token": "", "host": "", "publicChannelAmount": "1000183", "channelAmount": "100000", "channelPrivate": false, "targetConf": "6", "minHtlcMsat": "600", "baseFeeMsat": "1000", "feeRate": "0.000001", "timeLockDelta": "144", "channelFeePermyriad": "40", "channelMinimumFeeMsat": "2000000", "additionalChannelCapacity": "100000", "maxInactiveDuration": "3888000", "lnd": { "address": "", "cert": "", "macaroon": "" } }, { "lspdPrivateKey": "", "token": "", "host": "", "publicChannelAmount": "1000183", "channelAmount": "100000", "channelPrivate": false, "targetConf": "6", "minHtlcMsat": "600", "baseFeeMsat": "1000", "feeRate": "0.000001", "timeLockDelta": "144", "channelFeePermyriad": "40", "channelMinimumFeeMsat": "2000000", "additionalChannelCapacity": "100000", "maxInactiveDuration": "3888000", "cln": { "pluginAddress": "
", "socketPath": "" } } ]' +NODES='[ { "name": "", "nodePubkey": "", "lspdPrivateKey": "", "token": "", "host": "", "publicChannelAmount": "1000183", "channelAmount": "100000", "channelPrivate": false, "targetConf": "6", "minHtlcMsat": "600", "baseFeeMsat": "1000", "feeRate": "0.000001", "timeLockDelta": "144", "channelFeePermyriad": "40", "channelMinimumFeeMsat": "2000000", "additionalChannelCapacity": "100000", "maxInactiveDuration": "3888000", "lnd": { "address": "", "cert": "", "macaroon": "" } }, { "name": "", "nodePubkey": "", "lspdPrivateKey": "", "token": "", "host": "", "publicChannelAmount": "1000183", "channelAmount": "100000", "channelPrivate": false, "targetConf": "6", "minHtlcMsat": "600", "baseFeeMsat": "1000", "feeRate": "0.000001", "timeLockDelta": "144", "channelFeePermyriad": "40", "channelMinimumFeeMsat": "2000000", "additionalChannelCapacity": "100000", "maxInactiveDuration": "3888000", "cln": { "pluginAddress": "
", "socketPath": "" } } ]' diff --git a/server.go b/server.go index cfae080..16e2069 100644 --- a/server.go +++ b/server.go @@ -37,8 +37,6 @@ type server struct { type node struct { client LightningClient - nodeName string - nodePubkey string nodeConfig *NodeConfig privateKey *btcec.PrivateKey publicKey *btcec.PublicKey @@ -54,8 +52,8 @@ func (s *server) ChannelInformation(ctx context.Context, in *lspdrpc.ChannelInfo } return &lspdrpc.ChannelInformationReply{ - Name: node.nodeName, - Pubkey: node.nodePubkey, + Name: node.nodeConfig.Name, + Pubkey: node.nodeConfig.NodePubkey, Host: node.nodeConfig.Host, ChannelCapacity: int64(node.nodeConfig.PublicChannelAmount), TargetConf: int32(node.nodeConfig.TargetConf), @@ -284,7 +282,6 @@ func NewGrpcServer(configs []*NodeConfig, address string, certmagicDomain string eciesPublicKey := eciesPrivateKey.PublicKey privateKey, publicKey := btcec.PrivKeyFromBytes(pk) - // TODO: Set nodename & nodepubkey node := &node{ nodeConfig: config, privateKey: privateKey, @@ -331,6 +328,23 @@ func NewGrpcServer(configs []*NodeConfig, address string, certmagicDomain string } func (s *server) 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 From 232420e1130b8391fc63ce521074add79354ef0a Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Mon, 9 Jan 2023 13:20:49 +0100 Subject: [PATCH 138/214] on shutdown stop receiving but keep sending --- cln_interceptor.go | 17 ++++++++++++++++- lnd_interceptor.go | 29 ++++++++++++++++++++++------- 2 files changed, 38 insertions(+), 8 deletions(-) diff --git a/cln_interceptor.go b/cln_interceptor.go index 083ce7b..7ef5e66 100644 --- a/cln_interceptor.go +++ b/cln_interceptor.go @@ -29,6 +29,7 @@ type ClnHtlcInterceptor struct { pluginClient proto.ClnPluginClient initWg sync.WaitGroup doneWg sync.WaitGroup + stopRequested bool ctx context.Context cancel context.CancelFunc } @@ -73,6 +74,7 @@ func (i *ClnHtlcInterceptor) Start() error { i.pluginClient = proto.NewClnPluginClient(conn) i.ctx = ctx i.cancel = cancel + i.stopRequested = false return i.intercept() } @@ -110,6 +112,13 @@ func (i *ClnHtlcInterceptor) intercept() error { i.initWg.Done() } + // Stop receiving if stop if requested. The defer func on top of this + // function will assure all htlcs that are currently being processed + // will complete. + if i.stopRequested { + return nil + } + request, err := interceptorClient.Recv() if err != nil { // If it is just the error result of the context cancellation @@ -169,8 +178,14 @@ func (i *ClnHtlcInterceptor) intercept() error { } func (i *ClnHtlcInterceptor) Stop() error { - i.cancel() + // Setting stopRequested to true will make the interceptor stop receiving. + i.stopRequested = true + + // Wait until all already received htlcs are handled, responses sent back. i.doneWg.Wait() + + // Close the grpc connection. + i.cancel() return nil } diff --git a/lnd_interceptor.go b/lnd_interceptor.go index 3cc152c..e89ca4c 100644 --- a/lnd_interceptor.go +++ b/lnd_interceptor.go @@ -14,12 +14,13 @@ import ( ) type LndHtlcInterceptor struct { - config *NodeConfig - client *LndClient - initWg sync.WaitGroup - doneWg sync.WaitGroup - ctx context.Context - cancel context.CancelFunc + config *NodeConfig + client *LndClient + stopRequested bool + initWg sync.WaitGroup + doneWg sync.WaitGroup + ctx context.Context + cancel context.CancelFunc } func NewLndHtlcInterceptor(conf *NodeConfig) (*LndHtlcInterceptor, error) { @@ -44,6 +45,7 @@ func (i *LndHtlcInterceptor) Start() error { ctx, cancel := context.WithCancel(context.Background()) i.ctx = ctx i.cancel = cancel + i.stopRequested = false go forwardingHistorySynchronize(ctx, i.client) go channelsSynchronize(ctx, i.client) @@ -51,8 +53,14 @@ func (i *LndHtlcInterceptor) Start() error { } func (i *LndHtlcInterceptor) Stop() error { - i.cancel() + // Setting stopRequested to true will make the interceptor stop receiving. + i.stopRequested = true + + // Wait until all already received htlcs are handled, responses sent back. i.doneWg.Wait() + + // Close the grpc connection. + i.cancel() return nil } @@ -93,6 +101,13 @@ func (i *LndHtlcInterceptor) intercept() error { i.initWg.Done() } + // Stop receiving if stop if requested. The defer func on top of this + // function will assure all htlcs that are currently being processed + // will complete. + if i.stopRequested { + return nil + } + request, err := interceptorClient.Recv() if err != nil { // If it is just the error result of the context cancellation From 353a5d427862bce7d1314beeabadd6f95d961e0a Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Mon, 9 Jan 2023 13:26:23 +0100 Subject: [PATCH 139/214] intercept stopsignals SIGINT and SIGTERM --- main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.go b/main.go index f28ea75..4ba23ae 100644 --- a/main.go +++ b/main.go @@ -112,7 +112,7 @@ func main() { }() c := make(chan os.Signal, 1) - signal.Notify(c, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT) + signal.Notify(c, syscall.SIGINT, syscall.SIGTERM) go func() { sig := <-c log.Printf("Received stop signal %v. Stopping.", sig) From 07cea44907b27da7a1013af9f8d340ae1085643e Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Mon, 9 Jan 2023 13:28:53 +0100 Subject: [PATCH 140/214] fix non-asserting assertion --- itest/intercept_zero_conf_test.go | 4 ++-- itest/zero_reserve_test.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/itest/intercept_zero_conf_test.go b/itest/intercept_zero_conf_test.go index 45ba809..0aec482 100644 --- a/itest/intercept_zero_conf_test.go +++ b/itest/intercept_zero_conf_test.go @@ -59,7 +59,7 @@ func testOpenZeroConfChannelOnReceive(p *testParams) { // Make sure capacity is correct chans := p.BreezClient().Node().GetChannels() - assert.Len(p.t, chans, 1) + assert.Equal(p.t, 1, len(chans)) c := chans[0] AssertChannelCapacity(p.t, outerAmountMsat, c.CapacityMsat) } @@ -114,7 +114,7 @@ func testOpenZeroConfSingleHtlc(p *testParams) { // Make sure capacity is correct chans := p.BreezClient().Node().GetChannels() - assert.Len(p.t, chans, 1) + assert.Equal(p.t, 1, len(chans)) c := chans[0] AssertChannelCapacity(p.t, outerAmountMsat, c.CapacityMsat) } diff --git a/itest/zero_reserve_test.go b/itest/zero_reserve_test.go index 8dd4fdf..81ffdee 100644 --- a/itest/zero_reserve_test.go +++ b/itest/zero_reserve_test.go @@ -54,7 +54,7 @@ func testZeroReserve(p *testParams) { // Make sure balance is correct chans := p.BreezClient().Node().GetChannels() - assert.Len(p.t, chans, 1) + assert.Equal(p.t, 1, len(chans)) c := chans[0] assert.Equal(p.t, c.RemoteReserveMsat, c.CapacityMsat/100) From 001b4a5dbd5dc8cc76d915c2d958b0e4e527f788 Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Mon, 9 Jan 2023 13:42:13 +0100 Subject: [PATCH 141/214] add logs to debug invalid signature in test --- itest/breez_client.go | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/itest/breez_client.go b/itest/breez_client.go index 8334acd..a510959 100644 --- a/itest/breez_client.go +++ b/itest/breez_client.go @@ -2,6 +2,7 @@ package itest import ( "crypto/sha256" + "log" "github.com/breez/lntest" "github.com/btcsuite/btcd/btcec/v2" @@ -61,10 +62,24 @@ func GenerateInvoices(n BreezClient, req generateInvoicesRequest) (invoice, invo }, }) + log.Printf( + "Encoding outer invoice. privkey: '%x', invoice: '%+v', original bolt11: '%s'", + n.Node().PrivateKey().Serialize(), + outerInvoiceRaw, + innerInvoice.Bolt11, + ) outerInvoice, err := outerInvoiceRaw.Encode(zpay32.MessageSigner{ SignCompact: func(msg []byte) ([]byte, error) { hash := sha256.Sum256(msg) - return ecdsa.SignCompact(n.Node().PrivateKey(), hash[:], true) + sig, err := ecdsa.SignCompact(n.Node().PrivateKey(), hash[:], true) + log.Printf( + "sign outer invoice. msg: '%x', hash: '%x', sig: '%x', err: %v", + msg, + hash, + sig, + err, + ) + return sig, err }, }) lntest.CheckError(n.Harness().T, err) From 1cb49f289689e715f93c57e060df97d6dc1d5a7d Mon Sep 17 00:00:00 2001 From: Yaacov Akiba Slama Date: Mon, 9 Jan 2023 22:23:58 +0200 Subject: [PATCH 142/214] Put directly the pem in the json configuration --- itest/lnd_lspd_node.go | 25 ++++++++++++------------- lnd_client.go | 10 ++-------- 2 files changed, 14 insertions(+), 21 deletions(-) diff --git a/itest/lnd_lspd_node.go b/itest/lnd_lspd_node.go index 01115c0..b306f4e 100644 --- a/itest/lnd_lspd_node.go +++ b/itest/lnd_lspd_node.go @@ -1,7 +1,8 @@ package itest import ( - "encoding/base64" + "encoding/hex" + "encoding/json" "fmt" "log" "os" @@ -48,12 +49,11 @@ func NewLndLspdNode(h *lntest.TestHarness, m *lntest.Miner, name string) LspNode } lightningNode := lntest.NewLndNode(h, m, name, args...) - lnd := fmt.Sprintf( - `{ "address": "%s", "cert": "%s", "macaroon": "%x" }`, - lightningNode.GrpcHost(), - base64.StdEncoding.EncodeToString(lightningNode.TlsCert()), - lightningNode.Macaroon(), - ) + j, _ := json.Marshal(map[string]string{ + "address": lightningNode.GrpcHost(), + "cert": lightningNode.TlsCert(), + "macaroon": hex.EncodeToString(lightningNode.Macaroon())}) + lnd := string(j) lspBase, err := newLspd(h, name, &lnd, nil) if err != nil { h.T.Fatalf("failed to initialize lspd") @@ -113,12 +113,11 @@ func (c *LndLspNode) Start() { split := strings.Split(string(scriptFile), "\n") for i, s := range split { if strings.HasPrefix(s, "export NODES") { - ext := fmt.Sprintf( - `"lnd": { "address": "%s", "cert": "%s", "macaroon": "%x" }}]'`, - c.lightningNode.GrpcHost(), - base64.StdEncoding.EncodeToString(c.lightningNode.TlsCert()), - c.lightningNode.Macaroon(), - ) + j, _ := json.Marshal(map[string]string{ + "address": c.lightningNode.GrpcHost(), + "cert": c.lightningNode.TlsCert(), + "macaroon": hex.EncodeToString(c.lightningNode.Macaroon())}) + ext := fmt.Sprintf(`"lnd": %s}]`, string(j)) start, _, _ := strings.Cut(s, `"lnd"`) split[i] = start + ext diff --git a/lnd_client.go b/lnd_client.go index 8bfe813..82e4869 100644 --- a/lnd_client.go +++ b/lnd_client.go @@ -3,7 +3,6 @@ package main import ( "context" "crypto/x509" - "encoding/base64" "encoding/hex" "fmt" "log" @@ -26,19 +25,14 @@ type LndClient struct { } func NewLndClient(conf *LndConfig) (*LndClient, error) { - cert, err := base64.StdEncoding.DecodeString(conf.Cert) - if err != nil { - return nil, fmt.Errorf("failed to decode cert: %w", err) - } - - _, err = hex.DecodeString(conf.Macaroon) + _, 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(cert) { + if !cp.AppendCertsFromPEM([]byte(conf.Cert)) { return nil, fmt.Errorf("credentials: failed to append certificates") } creds := credentials.NewClientTLSFromCert(cp, "") From 848801267a58b55a99af52e5b83513182a1c19d3 Mon Sep 17 00:00:00 2001 From: Yaacov Akiba Slama Date: Tue, 17 Jan 2023 16:25:40 +0200 Subject: [PATCH 143/214] Open a channel only if the nextHop is unknown or the destination --- cln_interceptor.go | 19 +++++++++++++++++-- intercept.go | 4 ++-- lnd_interceptor.go | 16 ++++++++++++++-- 3 files changed, 33 insertions(+), 6 deletions(-) diff --git a/cln_interceptor.go b/cln_interceptor.go index 7ef5e66..b29fcbc 100644 --- a/cln_interceptor.go +++ b/cln_interceptor.go @@ -133,11 +133,26 @@ func (i *ClnHtlcInterceptor) intercept() error { log.Printf("unexpected error in interceptor.Recv() %v", err) break } + nextHop := "" + channels, err := i.client.client.GetChannel(request.Onion.ShortChannelId) + if err != nil { + for _, c := range channels { + if c.Source == i.config.NodePubkey { + nextHop = c.Destination + break + } + if c.Destination == i.config.NodePubkey { + nextHop = c.Source + break + } + } + } - log.Printf("correlationid: %v\nhtlc: %v\nchanID: %v\nincoming amount: %v\noutgoing amount: %v\nincoming expiry: %v\noutgoing expiry: %v\npaymentHash: %v\nonionBlob: %v\n\n", + log.Printf("correlationid: %v\nhtlc: %v\nchanID: %v\nnextHop: %v\nincoming amount: %v\noutgoing amount: %v\nincoming expiry: %v\noutgoing expiry: %v\npaymentHash: %v\nonionBlob: %v\n\n", request.Correlationid, request.Htlc, request.Onion.ShortChannelId, + nextHop, request.Htlc.AmountMsat, //with fees request.Onion.ForwardMsat, request.Htlc.CltvExpiryRelative, @@ -153,7 +168,7 @@ func (i *ClnHtlcInterceptor) intercept() error { interceptorClient.Send(i.defaultResolution(request)) i.doneWg.Done() } - interceptResult := intercept(i.client, i.config, paymentHash, request.Onion.ForwardMsat, request.Htlc.CltvExpiry) + interceptResult := intercept(i.client, i.config, nextHop, paymentHash, request.Onion.ForwardMsat, request.Htlc.CltvExpiry) switch interceptResult.action { case INTERCEPT_RESUME_WITH_ONION: interceptorClient.Send(i.resumeWithOnion(request, interceptResult)) diff --git a/intercept.go b/intercept.go index d15c861..14a9305 100644 --- a/intercept.go +++ b/intercept.go @@ -45,7 +45,7 @@ type interceptResult struct { onionBlob []byte } -func intercept(client LightningClient, config *NodeConfig, reqPaymentHash []byte, reqOutgoingAmountMsat uint64, reqOutgoingExpiry uint32) interceptResult { +func intercept(client LightningClient, config *NodeConfig, nextHop string, reqPaymentHash []byte, reqOutgoingAmountMsat uint64, reqOutgoingExpiry uint32) interceptResult { reqPaymentHashStr := hex.EncodeToString(reqPaymentHash) resp, _, _ := payHashGroup.Do(reqPaymentHashStr, func() (interface{}, error) { paymentHash, paymentSecret, destination, incomingAmountMsat, outgoingAmountMsat, channelPoint, err := paymentInfo(reqPaymentHash) @@ -58,7 +58,7 @@ func intercept(client LightningClient, config *NodeConfig, reqPaymentHash []byte } log.Printf("paymentHash:%x\npaymentSecret:%x\ndestination:%x\nincomingAmountMsat:%v\noutgoingAmountMsat:%v", paymentHash, paymentSecret, destination, incomingAmountMsat, outgoingAmountMsat) - if paymentSecret == nil { + if paymentSecret == nil || (nextHop != "" && nextHop != hex.EncodeToString(destination)) { return interceptResult{ action: INTERCEPT_RESUME, }, nil diff --git a/lnd_interceptor.go b/lnd_interceptor.go index e89ca4c..5e0f812 100644 --- a/lnd_interceptor.go +++ b/lnd_interceptor.go @@ -123,9 +123,21 @@ func (i *LndHtlcInterceptor) intercept() error { break } - fmt.Printf("htlc: %v\nchanID: %v\nincoming amount: %v\noutgoing amount: %v\nincomin expiry: %v\noutgoing expiry: %v\npaymentHash: %x\nonionBlob: %x\n\n", + nextHop := "" + chanInfo, err := i.client.client.GetChanInfo(context.Background(), &lnrpc.ChanInfoRequest{ChanId: request.OutgoingRequestedChanId}) + if err == nil && chanInfo != nil { + if chanInfo.Node1Pub == i.config.NodePubkey { + nextHop = chanInfo.Node2Pub + } + if chanInfo.Node2Pub == i.config.NodePubkey { + nextHop = chanInfo.Node1Pub + } + } + + fmt.Printf("htlc: %v\nchanID: %v\nnextHop: %v\nincoming amount: %v\noutgoing amount: %v\nincomin expiry: %v\noutgoing expiry: %v\npaymentHash: %x\nonionBlob: %x\n\n", request.IncomingCircuitKey.HtlcId, request.IncomingCircuitKey.ChanId, + nextHop, request.IncomingAmountMsat, request.OutgoingAmountMsat, request.IncomingExpiry, @@ -136,7 +148,7 @@ func (i *LndHtlcInterceptor) intercept() error { i.doneWg.Add(1) go func() { - interceptResult := intercept(i.client, i.config, request.PaymentHash, request.OutgoingAmountMsat, request.OutgoingExpiry) + interceptResult := intercept(i.client, i.config, nextHop, request.PaymentHash, request.OutgoingAmountMsat, request.OutgoingExpiry) switch interceptResult.action { case INTERCEPT_RESUME_WITH_ONION: interceptorClient.Send(&routerrpc.ForwardHtlcInterceptResponse{ From b83019e5c6b73995e39f86060c289054d76ff100 Mon Sep 17 00:00:00 2001 From: Roei Erez Date: Wed, 18 Jan 2023 10:31:49 +0200 Subject: [PATCH 144/214] Remove invalid fake channels check. We no longer need to check for confirmed channels so now we return empty map instead to satisfy the client. This is untill we will release a new client that doesn't use this endpoint at all. --- db.go | 30 ------------------------------ server.go | 24 +----------------------- 2 files changed, 1 insertion(+), 53 deletions(-) diff --git a/db.go b/db.go index f982d69..4e40169 100644 --- a/db.go +++ b/db.go @@ -2,7 +2,6 @@ package main import ( "context" - "encoding/hex" "fmt" "log" "time" @@ -101,35 +100,6 @@ func insertChannel(initialChanID, confirmedChanId uint64, channelPoint string, n return nil } -func confirmedChannels(sNodeID string) (map[string]uint64, error) { - nodeID, err := hex.DecodeString(sNodeID) - if err != nil { - return nil, fmt.Errorf("hex.DecodeString(%v) error: %w", sNodeID, err) - } - rows, err := pgxPool.Query(context.Background(), - `SELECT confirmed_chanid, channel_point - FROM channels - WHERE nodeid=$1 AND confirmed_chanid IS NOT NULL`, - nodeID) - if err != nil { - return nil, fmt.Errorf("channels(%x) error: %w", nodeID, err) - } - defer rows.Close() - chans := make(map[string]uint64) - for rows.Next() { - var ( - chanID int64 - channelPoint string - ) - err = rows.Scan(&chanID, &channelPoint) - if err != nil { - return nil, fmt.Errorf("channels(%x) rows.Scan error: %w", nodeID, err) - } - chans[channelPoint] = uint64(chanID) - } - return chans, rows.Err() -} - func lastForwardingEvent() (int64, error) { var last int64 err := pgxPool.QueryRow(context.Background(), diff --git a/server.go b/server.go index 16e2069..a02ab98 100644 --- a/server.go +++ b/server.go @@ -206,18 +206,13 @@ func (s *server) CheckChannels(ctx context.Context, in *lspdrpc.Encrypted) (*lsp 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 := 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) } checkChannelsReply := lspdrpc.CheckChannelsReply{ - NotFakeChannels: notFakeChannels, + NotFakeChannels: make(map[string]uint64), ClosedChannels: closedChannels, } dataReply, err := proto.Marshal(&checkChannelsReply) @@ -249,23 +244,6 @@ func (s *server) CheckChannels(ctx context.Context, in *lspdrpc.Encrypted) (*lsp 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 NewGrpcServer(configs []*NodeConfig, address string, certmagicDomain string) (*server, error) { if len(configs) == 0 { return nil, fmt.Errorf("no nodes supplied") From 7b93af7433be694cd331fb47cb0d29c4a6108098 Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Fri, 20 Jan 2023 16:36:05 +0100 Subject: [PATCH 145/214] fix tls cert encoding in itests --- itest/lnd_lspd_node.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/itest/lnd_lspd_node.go b/itest/lnd_lspd_node.go index b306f4e..601a83a 100644 --- a/itest/lnd_lspd_node.go +++ b/itest/lnd_lspd_node.go @@ -51,7 +51,7 @@ func NewLndLspdNode(h *lntest.TestHarness, m *lntest.Miner, name string) LspNode lightningNode := lntest.NewLndNode(h, m, name, args...) j, _ := json.Marshal(map[string]string{ "address": lightningNode.GrpcHost(), - "cert": lightningNode.TlsCert(), + "cert": string(lightningNode.TlsCert()), "macaroon": hex.EncodeToString(lightningNode.Macaroon())}) lnd := string(j) lspBase, err := newLspd(h, name, &lnd, nil) @@ -115,9 +115,9 @@ func (c *LndLspNode) Start() { if strings.HasPrefix(s, "export NODES") { j, _ := json.Marshal(map[string]string{ "address": c.lightningNode.GrpcHost(), - "cert": c.lightningNode.TlsCert(), + "cert": string(c.lightningNode.TlsCert()), "macaroon": hex.EncodeToString(c.lightningNode.Macaroon())}) - ext := fmt.Sprintf(`"lnd": %s}]`, string(j)) + ext := fmt.Sprintf(`"lnd": %s}]'`, string(j)) start, _, _ := strings.Cut(s, `"lnd"`) split[i] = start + ext From 5aad6b313eae9edfc6c7f29f686b24ad6fadcaf2 Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Mon, 23 Jan 2023 14:54:55 +0100 Subject: [PATCH 146/214] handle clean shutdown --- cln_plugin/cln_plugin.go | 4 ++++ cln_plugin/cmd/main.go | 9 +++++++++ cln_plugin/server.go | 7 ++++++- go.mod | 2 +- itest/cln_lspd_node.go | 4 +++- itest/lnd_lspd_node.go | 4 +++- 6 files changed, 26 insertions(+), 4 deletions(-) diff --git a/cln_plugin/cln_plugin.go b/cln_plugin/cln_plugin.go index 039fc9b..4ae8b4b 100644 --- a/cln_plugin/cln_plugin.go +++ b/cln_plugin/cln_plugin.go @@ -66,6 +66,10 @@ func (c *ClnPlugin) Start() { c.setupLogging() go c.listenRequests() <-c.done + s := c.server + if s != nil { + <-s.completed + } } // Stops the cln plugin. Drops any remaining work immediately. diff --git a/cln_plugin/cmd/main.go b/cln_plugin/cmd/main.go index 0fc934d..d358eb5 100644 --- a/cln_plugin/cmd/main.go +++ b/cln_plugin/cmd/main.go @@ -2,11 +2,20 @@ package main import ( "os" + "os/signal" + "syscall" "github.com/breez/lspd/cln_plugin" ) func main() { plugin := cln_plugin.NewClnPlugin(os.Stdin, os.Stdout) + c := make(chan os.Signal, 1) + signal.Notify(c, syscall.SIGINT, syscall.SIGTERM) + go func() { + <-c + // Stop everything gracefully on stop signal + plugin.Stop() + }() plugin.Start() } diff --git a/cln_plugin/server.go b/cln_plugin/server.go index e11a240..b03c97c 100644 --- a/cln_plugin/server.go +++ b/cln_plugin/server.go @@ -35,6 +35,7 @@ type server struct { newSubscriber chan struct{} started chan struct{} done chan struct{} + completed chan struct{} startError chan error sendQueue chan *htlcAcceptedMsg recvQueue chan *htlcResultMsg @@ -76,6 +77,7 @@ func (s *server) Start() error { } s.done = make(chan struct{}) + s.completed = make(chan struct{}) s.newSubscriber = make(chan struct{}) s.grpcServer = grpc.NewServer( grpc.KeepaliveParams(keepalive.ServerParameters{ @@ -93,7 +95,9 @@ func (s *server) Start() error { go s.listenHtlcRequests() go s.listenHtlcResponses() close(s.started) - return s.grpcServer.Serve(lis) + err = s.grpcServer.Serve(lis) + close(s.completed) + return err } // Waits until the server has started, or errored during startup. @@ -119,6 +123,7 @@ func (s *server) Stop() { s.grpcServer = nil close(s.done) + <-s.completed log.Printf("Server stopped.") } diff --git a/go.mod b/go.mod index 65f21e4..e5af55e 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.19 require ( github.com/aws/aws-sdk-go v1.30.20 - github.com/breez/lntest v0.0.17 + github.com/breez/lntest v0.0.18 github.com/btcsuite/btcd v0.23.3 github.com/btcsuite/btcd/btcec/v2 v2.2.1 github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 diff --git a/itest/cln_lspd_node.go b/itest/cln_lspd_node.go index 4080523..0221e84 100644 --- a/itest/cln_lspd_node.go +++ b/itest/cln_lspd_node.go @@ -8,6 +8,7 @@ import ( "os/exec" "path/filepath" "sync" + "syscall" "github.com/breez/lntest" lspd "github.com/breez/lspd/rpc" @@ -109,6 +110,7 @@ func (c *ClnLspNode) Start() { }) cmd := exec.CommandContext(c.harness.Ctx, c.lspBase.scriptFilePath) + cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} logFile, err := os.Create(c.logFilePath) if err != nil { lntest.PerformCleanup(cleanups) @@ -136,7 +138,7 @@ func (c *ClnLspNode) Start() { return nil } - proc.Kill() + syscall.Kill(-proc.Pid, syscall.SIGINT) log.Printf("About to wait for lspd to exit") status, err := proc.Wait() diff --git a/itest/lnd_lspd_node.go b/itest/lnd_lspd_node.go index 601a83a..e9c0d10 100644 --- a/itest/lnd_lspd_node.go +++ b/itest/lnd_lspd_node.go @@ -10,6 +10,7 @@ import ( "path/filepath" "strings" "sync" + "syscall" "github.com/breez/lntest" lspd "github.com/breez/lspd/rpc" @@ -132,6 +133,7 @@ func (c *LndLspNode) Start() { } cmd := exec.CommandContext(c.harness.Ctx, c.lspBase.scriptFilePath) + cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} logFile, err := os.Create(c.logFilePath) if err != nil { lntest.PerformCleanup(cleanups) @@ -159,7 +161,7 @@ func (c *LndLspNode) Start() { return nil } - proc.Kill() + syscall.Kill(-proc.Pid, syscall.SIGINT) log.Printf("About to wait for lspd to exit") status, err := proc.Wait() From 43e045f7ff2860335ebb286591d59e7ff1f3bf4d Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Mon, 30 Jan 2023 11:53:33 +0100 Subject: [PATCH 147/214] respect the lsp timelockdelta --- cln_interceptor.go | 2 +- intercept.go | 9 ++++++- itest/cltv_test.go | 58 ++++++++++++++++++++++++++++++++++++++++++++++ itest/lspd_node.go | 2 +- itest/lspd_test.go | 4 ++++ lnd_interceptor.go | 2 +- 6 files changed, 73 insertions(+), 4 deletions(-) create mode 100644 itest/cltv_test.go diff --git a/cln_interceptor.go b/cln_interceptor.go index b29fcbc..ffadb47 100644 --- a/cln_interceptor.go +++ b/cln_interceptor.go @@ -168,7 +168,7 @@ func (i *ClnHtlcInterceptor) intercept() error { interceptorClient.Send(i.defaultResolution(request)) i.doneWg.Done() } - interceptResult := intercept(i.client, i.config, nextHop, paymentHash, request.Onion.ForwardMsat, request.Htlc.CltvExpiry) + interceptResult := intercept(i.client, i.config, nextHop, paymentHash, request.Onion.ForwardMsat, request.Onion.OutgoingCltvValue, request.Htlc.CltvExpiry) switch interceptResult.action { case INTERCEPT_RESUME_WITH_ONION: interceptorClient.Send(i.resumeWithOnion(request, interceptResult)) diff --git a/intercept.go b/intercept.go index 14a9305..a8ecc75 100644 --- a/intercept.go +++ b/intercept.go @@ -45,7 +45,7 @@ type interceptResult struct { onionBlob []byte } -func intercept(client LightningClient, config *NodeConfig, nextHop string, reqPaymentHash []byte, reqOutgoingAmountMsat uint64, reqOutgoingExpiry uint32) interceptResult { +func intercept(client LightningClient, config *NodeConfig, nextHop string, reqPaymentHash []byte, reqOutgoingAmountMsat uint64, reqOutgoingExpiry uint32, reqIncomingExpiry uint32) interceptResult { reqPaymentHashStr := hex.EncodeToString(reqPaymentHash) resp, _, _ := payHashGroup.Do(reqPaymentHashStr, func() (interface{}, error) { paymentHash, paymentSecret, destination, incomingAmountMsat, outgoingAmountMsat, channelPoint, err := paymentInfo(reqPaymentHash) @@ -66,6 +66,13 @@ func intercept(client LightningClient, config *NodeConfig, nextHop string, reqPa if channelPoint == nil { if bytes.Equal(paymentHash, reqPaymentHash) { + if int64(reqIncomingExpiry)-int64(reqOutgoingExpiry) < int64(config.TimeLockDelta) { + return interceptResult{ + action: INTERCEPT_FAIL_HTLC_WITH_CODE, + failureCode: FAILURE_TEMPORARY_CHANNEL_FAILURE, + }, nil + } + channelPoint, err = openChannel(client, config, reqPaymentHash, destination, incomingAmountMsat) if err != nil { log.Printf("openChannel(%x, %v) err: %v", destination, incomingAmountMsat, err) diff --git a/itest/cltv_test.go b/itest/cltv_test.go new file mode 100644 index 0000000..7bc9eaf --- /dev/null +++ b/itest/cltv_test.go @@ -0,0 +1,58 @@ +package itest + +import ( + "log" + "time" + + "github.com/breez/lntest" + lspd "github.com/breez/lspd/rpc" + "github.com/stretchr/testify/assert" +) + +func testInvalidCltv(p *testParams) { + alice := lntest.NewClnNode(p.h, p.m, "Alice") + alice.Start() + alice.Fund(10000000) + p.lsp.LightningNode().Fund(10000000) + + log.Print("Opening channel between Alice and the lsp") + channel := alice.OpenChannel(p.lsp.LightningNode(), &lntest.OpenChannelOptions{ + AmountSat: publicChanAmount, + }) + channelId := alice.WaitForChannelReady(channel) + + log.Printf("Adding bob's invoices") + outerAmountMsat := uint64(2100000) + innerAmountMsat, lspAmountMsat := calculateInnerAmountMsat(p.lsp, outerAmountMsat) + description := "Please pay me" + innerInvoice, outerInvoice := GenerateInvoices(p.BreezClient(), + generateInvoicesRequest{ + innerAmountMsat: innerAmountMsat, + outerAmountMsat: outerAmountMsat, + description: description, + lsp: p.lsp, + }) + + log.Print("Connecting bob to lspd") + p.BreezClient().Node().ConnectPeer(p.lsp.LightningNode()) + + log.Printf("Registering payment with lsp") + RegisterPayment(p.lsp, &lspd.PaymentInformation{ + PaymentHash: innerInvoice.paymentHash, + PaymentSecret: innerInvoice.paymentSecret, + Destination: p.BreezClient().Node().NodeId(), + IncomingAmountMsat: int64(outerAmountMsat), + OutgoingAmountMsat: int64(lspAmountMsat), + }) + + // TODO: Fix race waiting for htlc interceptor. + log.Printf("Waiting %v to allow htlc interceptor to activate.", htlcInterceptorDelay) + <-time.After(htlcInterceptorDelay) + log.Printf("Alice paying") + route := constructRoute(p.lsp.LightningNode(), p.BreezClient().Node(), channelId, lntest.NewShortChanIDFromString("1x0x0"), outerAmountMsat) + + // Decrement the delay in the first hop, so the cltv delta will become 143 (too little) + route.Hops[0].Delay-- + _, err := alice.PayViaRoute(outerAmountMsat, outerInvoice.paymentHash, outerInvoice.paymentSecret, route) + assert.Contains(p.t, err.Error(), "WIRE_TEMPORARY_CHANNEL_FAILURE") +} diff --git a/itest/lspd_node.go b/itest/lspd_node.go index df1fc15..0b47bd0 100644 --- a/itest/lspd_node.go +++ b/itest/lspd_node.go @@ -30,7 +30,7 @@ var ( var ( lspBaseFeeMsat uint32 = 1000 lspFeeRatePpm uint32 = 1 - lspCltvDelta uint16 = 40 + lspCltvDelta uint16 = 144 ) type LspNode interface { diff --git a/itest/lspd_test.go b/itest/lspd_test.go index 4c69ad4..b3220f9 100644 --- a/itest/lspd_test.go +++ b/itest/lspd_test.go @@ -97,4 +97,8 @@ var allTestCases = []*testCase{ name: "testProbing", test: testProbing, }, + { + name: "testInvalidCltv", + test: testInvalidCltv, + }, } diff --git a/lnd_interceptor.go b/lnd_interceptor.go index 5e0f812..c474e93 100644 --- a/lnd_interceptor.go +++ b/lnd_interceptor.go @@ -148,7 +148,7 @@ func (i *LndHtlcInterceptor) intercept() error { i.doneWg.Add(1) go func() { - interceptResult := intercept(i.client, i.config, nextHop, request.PaymentHash, request.OutgoingAmountMsat, request.OutgoingExpiry) + interceptResult := intercept(i.client, i.config, nextHop, request.PaymentHash, request.OutgoingAmountMsat, request.OutgoingExpiry, request.IncomingExpiry) switch interceptResult.action { case INTERCEPT_RESUME_WITH_ONION: interceptorClient.Send(&routerrpc.ForwardHtlcInterceptResponse{ From 17a3dc1d9435d441f138704b2487b85dfcdeb79f Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Thu, 2 Feb 2023 15:38:23 +0100 Subject: [PATCH 148/214] add a mempool client for fee estimation --- chain/fee_estimator.go | 21 +++++++++ mempool/mempool_client.go | 90 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 111 insertions(+) create mode 100644 chain/fee_estimator.go create mode 100644 mempool/mempool_client.go diff --git a/chain/fee_estimator.go b/chain/fee_estimator.go new file mode 100644 index 0000000..8560a68 --- /dev/null +++ b/chain/fee_estimator.go @@ -0,0 +1,21 @@ +package chain + +import "context" + +type FeeStrategy int + +const ( + FeeStrategyFastest FeeStrategy = 0 + FeeStrategyHalfHour FeeStrategy = 1 + FeeStrategyHour FeeStrategy = 2 + FeeStrategyEconomy FeeStrategy = 3 + FeeStrategyMinimum FeeStrategy = 4 +) + +type FeeEstimation struct { + SatPerVByte float64 +} + +type FeeEstimator interface { + EstimateFeeRate(context.Context, FeeStrategy) (*FeeEstimation, error) +} diff --git a/mempool/mempool_client.go b/mempool/mempool_client.go new file mode 100644 index 0000000..f24e747 --- /dev/null +++ b/mempool/mempool_client.go @@ -0,0 +1,90 @@ +package mempool + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "strings" + + "github.com/breez/lspd/chain" +) + +type MempoolClient struct { + apiBaseUrl string + httpClient *http.Client +} + +type RecommendedFeesResponse struct { + FastestFee float64 `json:"fastestFee"` + HalfHourFee float64 `json:"halfHourFee"` + HourFee float64 `json:"hourFee"` + EconomyFee float64 `json:"economyFee"` + MinimumFee float64 `json:"minimumFee"` +} + +func NewMempoolClient(apiBaseUrl string) (*MempoolClient, error) { + if apiBaseUrl == "" { + return nil, fmt.Errorf("apiBaseUrl not set") + } + + if !strings.HasSuffix(apiBaseUrl, "/") { + apiBaseUrl = apiBaseUrl + "/" + } + + return &MempoolClient{ + apiBaseUrl: apiBaseUrl, + httpClient: http.DefaultClient, + }, nil +} + +func (m *MempoolClient) EstimateFeeRate( + ctx context.Context, + strategy chain.FeeStrategy, +) (*chain.FeeEstimation, error) { + req, err := http.NewRequestWithContext( + ctx, + "GET", + m.apiBaseUrl+"fees/recommended", + nil, + ) + if err != nil { + return nil, fmt.Errorf("http.NewRequestWithContext error: %w", err) + } + + resp, err := m.httpClient.Do(req) + if err != nil { + return nil, fmt.Errorf("httpClient.Do error: %w", err) + } + + defer resp.Body.Close() + if !(resp.StatusCode >= 200 && resp.StatusCode < 300) { + return nil, fmt.Errorf("error statuscode %v: %w", resp.StatusCode, err) + } + + var body RecommendedFeesResponse + err = json.NewDecoder(resp.Body).Decode(&body) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal response: %w", err) + } + + var rate float64 + switch strategy { + case chain.FeeStrategyFastest: + rate = body.FastestFee + case chain.FeeStrategyHalfHour: + rate = body.HalfHourFee + case chain.FeeStrategyHour: + rate = body.HourFee + case chain.FeeStrategyEconomy: + rate = body.EconomyFee + case chain.FeeStrategyMinimum: + rate = body.MinimumFee + default: + return nil, fmt.Errorf("unsupported fee strategy: %v", strategy) + } + + return &chain.FeeEstimation{ + SatPerVByte: rate, + }, nil +} From bfb25ae4bb3ca193cf0329f696f102c12f653f0f Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Mon, 30 Jan 2023 13:47:27 +0100 Subject: [PATCH 149/214] use mempool client for fee estimation --- cln_client.go | 34 +++++++++++++++++++++++++--------- intercept.go | 42 +++++++++++++++++++++++++++++++++++++----- itest/lspd_node.go | 2 ++ lightning_client.go | 14 ++++++++------ lnd_client.go | 13 ++++++++++--- main.go | 12 ++++++++++++ sample.env | 2 ++ server.go | 2 +- 8 files changed, 97 insertions(+), 24 deletions(-) diff --git a/cln_client.go b/cln_client.go index 04886ae..f99ef11 100644 --- a/cln_client.go +++ b/cln_client.go @@ -76,25 +76,41 @@ func (c *ClnClient) IsConnected(destination []byte) (bool, error) { func (c *ClnClient) OpenChannel(req *OpenChannelRequest) (*wire.OutPoint, error) { pubkey := hex.EncodeToString(req.Destination) - minConf := uint16(req.TargetConf) - if req.IsZeroConf { - minConf = 0 - } - + minConfs := uint16(req.MinConfs) var minDepth *uint16 if req.IsZeroConf { var d uint16 = 0 minDepth = &d } + var rate *glightning.FeeRate + if req.FeeSatPerVByte != nil { + rate = &glightning.FeeRate{ + Rate: uint(*req.FeeSatPerVByte * 1000), + Style: glightning.PerKb, + } + } else if req.TargetConf != nil { + if *req.TargetConf < 3 { + rate = &glightning.FeeRate{ + Directive: glightning.Urgent, + } + } else if *req.TargetConf < 30 { + rate = &glightning.FeeRate{ + Directive: glightning.Normal, + } + } else { + rate = &glightning.FeeRate{ + Directive: glightning.Slow, + } + } + } + fundResult, err := c.client.FundChannelExt( pubkey, glightning.NewSat(int(req.CapacitySat)), - &glightning.FeeRate{ - Directive: glightning.Slow, - }, + rate, !req.IsPrivate, - &minConf, + &minConfs, glightning.NewMsat(0), minDepth, glightning.NewSat(0), diff --git a/intercept.go b/intercept.go index a8ecc75..1e9dbff 100644 --- a/intercept.go +++ b/intercept.go @@ -2,12 +2,14 @@ package main import ( "bytes" + "context" "encoding/hex" "fmt" "log" "math/big" "time" + "github.com/breez/lspd/chain" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/wire" sphinx "github.com/lightningnetwork/lightning-onion" @@ -34,6 +36,7 @@ var ( ) var payHashGroup singleflight.Group +var feeEstimator chain.FeeEstimator type interceptResult struct { action interceptAction @@ -236,12 +239,41 @@ func openChannel(client LightningClient, config *NodeConfig, paymentHash, destin if capacity == config.PublicChannelAmount { capacity++ } + + var targetConf *uint32 + confStr := "" + var feeEstimation *float64 + feeStr := "" + if feeEstimator != nil { + fee, err := feeEstimator.EstimateFeeRate( + context.Background(), + chain.FeeStrategyMinimum, + ) + if err == nil { + feeEstimation = &fee.SatPerVByte + feeStr = fmt.Sprintf("%.5f", *feeEstimation) + } else { + log.Printf("Error estimating chain fee, fallback to target conf: %v", err) + targetConf = &config.TargetConf + confStr = fmt.Sprintf("%v", *targetConf) + } + } + + log.Printf( + "Opening zero conf channel. Destination: %x, capacity: %v, fee: %s, targetConf: %s", + destination, + capacity, + feeStr, + confStr, + ) channelPoint, err := client.OpenChannel(&OpenChannelRequest{ - Destination: destination, - CapacitySat: uint64(capacity), - TargetConf: 6, - IsPrivate: true, - IsZeroConf: true, + Destination: destination, + CapacitySat: uint64(capacity), + MinConfs: 6, + IsPrivate: true, + IsZeroConf: true, + FeeSatPerVByte: feeEstimation, + TargetConf: targetConf, }) if err != nil { log.Printf("client.OpenChannelSync(%x, %v) error: %v", destination, capacity, err) diff --git a/itest/lspd_node.go b/itest/lspd_node.go index 0b47bd0..2749094 100644 --- a/itest/lspd_node.go +++ b/itest/lspd_node.go @@ -110,6 +110,8 @@ func newLspd(h *lntest.TestHarness, name string, lnd *string, cln *string, envEx nodes, fmt.Sprintf("DATABASE_URL=%s", postgresBackend.ConnectionString()), fmt.Sprintf("LISTEN_ADDRESS=%s", grpcAddress), + fmt.Sprintf("USE_MEMPOOL_FEE_ESTIMATION=true"), + fmt.Sprintf("MEMPOOL_API_BASE_URL=https://mempool.space/api/v1/"), } env = append(env, envExt...) diff --git a/lightning_client.go b/lightning_client.go index 7f10124..7260d85 100644 --- a/lightning_client.go +++ b/lightning_client.go @@ -16,12 +16,14 @@ type GetChannelResult struct { } type OpenChannelRequest struct { - Destination []byte - CapacitySat uint64 - MinHtlcMsat uint64 - TargetConf uint32 - IsPrivate bool - IsZeroConf bool + Destination []byte + CapacitySat uint64 + MinHtlcMsat uint64 + IsPrivate bool + IsZeroConf bool + MinConfs uint32 + FeeSatPerVByte *float64 + TargetConf *uint32 } type LightningClient interface { diff --git a/lnd_client.go b/lnd_client.go index 82e4869..d8f087e 100644 --- a/lnd_client.go +++ b/lnd_client.go @@ -96,16 +96,23 @@ func (c *LndClient) IsConnected(destination []byte) (bool, error) { } func (c *LndClient) OpenChannel(req *OpenChannelRequest) (*wire.OutPoint, error) { - channelPoint, err := c.client.OpenChannelSync(context.Background(), &lnrpc.OpenChannelRequest{ + lnReq := &lnrpc.OpenChannelRequest{ NodePubkey: req.Destination, LocalFundingAmount: int64(req.CapacitySat), - TargetConf: int32(req.TargetConf), PushSat: 0, Private: req.IsPrivate, CommitmentType: lnrpc.CommitmentType_ANCHORS, ZeroConf: req.IsZeroConf, - }) + MinConfs: int32(req.MinConfs), + } + 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) diff --git a/main.go b/main.go index 4ba23ae..e55c4f3 100644 --- a/main.go +++ b/main.go @@ -9,6 +9,7 @@ import ( "sync" "syscall" + "github.com/breez/lspd/mempool" "github.com/btcsuite/btcd/btcec/v2" ) @@ -33,6 +34,17 @@ func main() { log.Fatalf("need at least one node configured in NODES.") } + useMempool := os.Getenv("USE_MEMPOOL_FEE_ESTIMATION") == "true" + if useMempool { + mempoolUrl := os.Getenv("MEMPOOL_API_BASE_URL") + feeEstimator, err = mempool.NewMempoolClient(mempoolUrl) + if err != nil { + log.Fatalf("failed to initialize mempool client: %v", err) + } + + log.Printf("using mempool api for fee estimation: %v", mempoolUrl) + } + var interceptors []HtlcInterceptor for _, node := range nodes { var interceptor HtlcInterceptor diff --git a/sample.env b/sample.env index 1057b32..5df5352 100644 --- a/sample.env +++ b/sample.env @@ -17,4 +17,6 @@ CHANNELMISMATCH_NOTIFICATION_TO='["Name1 "]' CHANNELMISMATCH_NOTIFICATION_CC='["Name2 ","Name3 "]' CHANNELMISMATCH_NOTIFICATION_FROM="Name4 " +USE_MEMPOOL_FEE_ESTIMATION=true +MEMPOOL_API_BASE_URL=https://mempool.space/api/v1/ NODES='[ { "name": "", "nodePubkey": "", "lspdPrivateKey": "", "token": "", "host": "", "publicChannelAmount": "1000183", "channelAmount": "100000", "channelPrivate": false, "targetConf": "6", "minHtlcMsat": "600", "baseFeeMsat": "1000", "feeRate": "0.000001", "timeLockDelta": "144", "channelFeePermyriad": "40", "channelMinimumFeeMsat": "2000000", "additionalChannelCapacity": "100000", "maxInactiveDuration": "3888000", "lnd": { "address": "", "cert": "", "macaroon": "" } }, { "name": "", "nodePubkey": "", "lspdPrivateKey": "", "token": "", "host": "", "publicChannelAmount": "1000183", "channelAmount": "100000", "channelPrivate": false, "targetConf": "6", "minHtlcMsat": "600", "baseFeeMsat": "1000", "feeRate": "0.000001", "timeLockDelta": "144", "channelFeePermyriad": "40", "channelMinimumFeeMsat": "2000000", "additionalChannelCapacity": "100000", "maxInactiveDuration": "3888000", "cln": { "pluginAddress": "
", "socketPath": "" } } ]' diff --git a/server.go b/server.go index a02ab98..e777784 100644 --- a/server.go +++ b/server.go @@ -127,7 +127,7 @@ func (s *server) OpenChannel(ctx context.Context, in *lspdrpc.OpenChannelRequest outPoint, err = node.client.OpenChannel(&OpenChannelRequest{ CapacitySat: node.nodeConfig.ChannelAmount, Destination: pubkey, - TargetConf: node.nodeConfig.TargetConf, + TargetConf: &node.nodeConfig.TargetConf, MinHtlcMsat: node.nodeConfig.MinHtlcMsat, IsPrivate: node.nodeConfig.ChannelPrivate, }) From 61bee1552cd2e127ffb218d100b964bf4ef05e81 Mon Sep 17 00:00:00 2001 From: Yaacov Akiba Slama Date: Thu, 9 Feb 2023 20:16:28 +0200 Subject: [PATCH 150/214] Add some documentation for the configuration environment variables --- config.go | 100 +++++++++++++++++++++++++++++++++++++++++------------ sample.env | 38 ++++++++++++++++++-- 2 files changed, 114 insertions(+), 24 deletions(-) diff --git a/config.go b/config.go index af9261b..7855caf 100644 --- a/config.go +++ b/config.go @@ -1,34 +1,90 @@ package main type NodeConfig struct { - Name string `json:name,omitempty` - NodePubkey string `json:nodePubkey,omitempty` - LspdPrivateKey string `json:"lspdPrivateKey"` - Token string `json:"token"` - Host string `json:"host"` - PublicChannelAmount int64 `json:"publicChannelAmount,string"` - ChannelAmount uint64 `json:"channelAmount,string"` - ChannelPrivate bool `json:"channelPrivate"` - TargetConf uint32 `json:"targetConf,string"` - MinHtlcMsat uint64 `json:"minHtlcMsat,string"` - BaseFeeMsat uint64 `json:"baseFeeMsat,string"` - FeeRate float64 `json:"feeRate,string"` - TimeLockDelta uint32 `json:"timeLockDelta,string"` - ChannelFeePermyriad int64 `json:"channelFeePermyriad,string"` - ChannelMinimumFeeMsat int64 `json:"channelMinimumFeeMsat,string"` - AdditionalChannelCapacity int64 `json:"additionalChannelCapacity,string"` - MaxInactiveDuration uint64 `json:"maxInactiveDuration,string"` - Lnd *LndConfig `json:"lnd,omitempty"` - Cln *ClnConfig `json:"cln,omitempty"` + // Name of the LSP. If empty, the node's alias will be taken instead. + Name string `json:name,omitempty` + + // The public key of the lightning node. + NodePubkey string `json:nodePubkey,omitempty` + + // Hex encoded private key of the LSP. This is used to decrypt traffic from + // clients. + LspdPrivateKey string `json:"lspdPrivateKey"` + + // Token used to authenticate to lspd. This token must be unique for each + // configured node, so it's obvious which node an rpc call is meant for. + Token string `json:"token"` + + // The network location of the lightning node, e.g. `12.34.56.78:9012` or + // `localhost:10011` + Host string `json:"host"` + + // Public channel amount is a reserved amount for public channels. If a + // zero conf channel is opened, it will never have this exact amount. + PublicChannelAmount int64 `json:"publicChannelAmount,string"` + + // The capacity of opened channels through the OpenChannel rpc. + ChannelAmount uint64 `json:"channelAmount,string"` + + // Value indicating whether channels opened through the OpenChannel rpc + // should be private. + ChannelPrivate bool `json:"channelPrivate"` + + // Number of blocks after which an opened channel is considered confirmed. + TargetConf uint32 `json:"targetConf,string"` + + // Smallest htlc amount routed over channels opened with the OpenChannel + // rpc call. + MinHtlcMsat uint64 `json:"minHtlcMsat,string"` + + // The base fee for routing payments over the channel. It is configured on + // the node itself, but this value is returned in the ChannelInformation rpc. + BaseFeeMsat uint64 `json:"baseFeeMsat,string"` + + // The fee rate for routing payments over the channel. It is configured on + // the node itself, but this value is returned in the ChannelInformation rpc. + FeeRate float64 `json:"feeRate,string"` + + // Minimum timelock delta required for opening a zero conf channel. + TimeLockDelta uint32 `json:"timeLockDelta,string"` + + // Fee for opening a zero conf channel in satoshi per 10000 satoshi based + // on the incoming payment amount. + ChannelFeePermyriad int64 `json:"channelFeePermyriad,string"` + + // Minimum fee for opening a zero conf channel in millisatoshi. + ChannelMinimumFeeMsat int64 `json:"channelMinimumFeeMsat,string"` + + // Channel capacity that is added on top of the incoming payment amount + // when a new zero conf channel is opened. In satoshi. + AdditionalChannelCapacity int64 `json:"additionalChannelCapacity,string"` + + // The channel can be closed if not used this duration in seconds. + MaxInactiveDuration uint64 `json:"maxInactiveDuration,string"` + + // Set this field to connect to an LND node. + Lnd *LndConfig `json:"lnd,omitempty"` + + // Set this field to connect to a CLN node. + Cln *ClnConfig `json:"cln,omitempty"` } type LndConfig struct { - Address string `json:"address"` - Cert string `json:"cert"` + // Address to the grpc api. + Address string `json:"address"` + + // tls cert for the grpc api. + Cert string `json:"cert"` + + // macaroon to use. Macaroon string `json:"macaroon"` } type ClnConfig struct { + // The address to the cln htlc acceptor grpc api shipped with lspd. PluginAddress string `json:"pluginAddress"` - SocketPath string `json:"socketPath"` + + // File path to the cln lightning-roc socket file. Find the path in + // cln-dir/mainnet/lightning-rpc + SocketPath string `json:"socketPath"` } diff --git a/sample.env b/sample.env index 5df5352..4a2d7ad 100644 --- a/sample.env +++ b/sample.env @@ -1,10 +1,23 @@ +# LISTEN_ADDRESS defines the host:port for the lspd grpc server. The best way is +# to use a local host:port and run lspd behind a reverse proxy like +# haproxy/nginx/caddy/traefik LISTEN_ADDRESS= -### If you define a domain here, the server will use certmagic to obtain -### a certificate from Let's Encrypt + +# You can also define a domain in CERTMAGIC_DOMAIN, and lspd will use certmagic +# to obtain a certificate from Let's Encrypt #CERTMAGIC_DOMAIN= +# DATABASE_URL is the postgresql db url in the form: +# postgres://username:password@host:port/dbname +# You can create the db and user with the following commands: +#CREATE ROLE ; +#ALTER ROLE WITH NOSUPERUSER INHERIT NOCREATEROLE NOCREATEDB LOGIN NOREPLICATION NOBYPASSRLS PASSWORD ''; +#CREATE DATABASE WITH TEMPLATE = template0 ENCODING = 'UTF8' LC_COLLATE = 'en_US.UTF-8' LC_CTYPE = 'en_US.UTF-8'; +#ALTER DATABASE OWNER TO ; DATABASE_URL= +# These variables are needed to send email using SES and the AWS_ACCESS_KEY_ID +# has to have the permission to send emails. AWS_REGION= AWS_ACCESS_KEY_ID= AWS_SECRET_ACCESS_KEY= @@ -17,6 +30,27 @@ CHANNELMISMATCH_NOTIFICATION_TO='["Name1 "]' CHANNELMISMATCH_NOTIFICATION_CC='["Name2 ","Name3 "]' CHANNELMISMATCH_NOTIFICATION_FROM="Name4 " +# By default lspd uses the fee estimation from the lightning node it is connected +# to for opening new channels. You can use mempool fee estimation instead by +# setting below variables. USE_MEMPOOL_FEE_ESTIMATION=true MEMPOOL_API_BASE_URL=https://mempool.space/api/v1/ + +# lspd can be connected to multiple nodes at once. The NODES variable takes an +# array of nodes. Each node is either a cln or an lnd node and should have the +# corresponding "cln" or "lnd" key set. +# +# TOKEN is a secret shared between the LSP (the lspd instance) and breez-server +# and is put in the header of each request. It should be unique for each node. +# You can generate it using for instance the command: openssl rand -base64 24 +# +# LSPD_PRIVATE_KEY is a key generated and printed in the console when you run +# ./lspd genkey". +# When sending the lspd information to the client (the app), lspd adds the +# public key corresponding to this private key and the client encrypt all the +# messages sent to the LSP using this public key. +# The goal is to hide anything from breez-server which is the pass "opaque" data +# from the app to lspd. +# +# For other specific settings see the fields in `config.go` NodeConfig struct. NODES='[ { "name": "", "nodePubkey": "", "lspdPrivateKey": "", "token": "", "host": "", "publicChannelAmount": "1000183", "channelAmount": "100000", "channelPrivate": false, "targetConf": "6", "minHtlcMsat": "600", "baseFeeMsat": "1000", "feeRate": "0.000001", "timeLockDelta": "144", "channelFeePermyriad": "40", "channelMinimumFeeMsat": "2000000", "additionalChannelCapacity": "100000", "maxInactiveDuration": "3888000", "lnd": { "address": "", "cert": "", "macaroon": "" } }, { "name": "", "nodePubkey": "", "lspdPrivateKey": "", "token": "", "host": "", "publicChannelAmount": "1000183", "channelAmount": "100000", "channelPrivate": false, "targetConf": "6", "minHtlcMsat": "600", "baseFeeMsat": "1000", "feeRate": "0.000001", "timeLockDelta": "144", "channelFeePermyriad": "40", "channelMinimumFeeMsat": "2000000", "additionalChannelCapacity": "100000", "maxInactiveDuration": "3888000", "cln": { "pluginAddress": "
", "socketPath": "" } } ]' From 466612f202b2242bfa87fda301a008b15b694d3c Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Mon, 13 Feb 2023 12:27:25 +0100 Subject: [PATCH 151/214] make mempool priority configurable --- config.go | 4 ++++ intercept.go | 5 +++-- itest/lspd_node.go | 3 ++- main.go | 19 ++++++++++++++++++- sample.env | 7 ++++++- 5 files changed, 33 insertions(+), 5 deletions(-) diff --git a/config.go b/config.go index 7855caf..1430cf6 100644 --- a/config.go +++ b/config.go @@ -33,6 +33,10 @@ type NodeConfig struct { // Number of blocks after which an opened channel is considered confirmed. TargetConf uint32 `json:"targetConf,string"` + // Minimum number of confirmations inputs for zero conf channel opens should + // have. + MinConfs uint32 `json:"minConfs,string"` + // Smallest htlc amount routed over channels opened with the OpenChannel // rpc call. MinHtlcMsat uint64 `json:"minHtlcMsat,string"` diff --git a/intercept.go b/intercept.go index 1e9dbff..dea1aa7 100644 --- a/intercept.go +++ b/intercept.go @@ -37,6 +37,7 @@ var ( var payHashGroup singleflight.Group var feeEstimator chain.FeeEstimator +var feeStrategy chain.FeeStrategy type interceptResult struct { action interceptAction @@ -247,7 +248,7 @@ func openChannel(client LightningClient, config *NodeConfig, paymentHash, destin if feeEstimator != nil { fee, err := feeEstimator.EstimateFeeRate( context.Background(), - chain.FeeStrategyMinimum, + feeStrategy, ) if err == nil { feeEstimation = &fee.SatPerVByte @@ -269,7 +270,7 @@ func openChannel(client LightningClient, config *NodeConfig, paymentHash, destin channelPoint, err := client.OpenChannel(&OpenChannelRequest{ Destination: destination, CapacitySat: uint64(capacity), - MinConfs: 6, + MinConfs: config.MinConfs, IsPrivate: true, IsZeroConf: true, FeeSatPerVByte: feeEstimation, diff --git a/itest/lspd_node.go b/itest/lspd_node.go index 2749094..abfab8b 100644 --- a/itest/lspd_node.go +++ b/itest/lspd_node.go @@ -99,7 +99,7 @@ func newLspd(h *lntest.TestHarness, name string, lnd *string, cln *string, envEx nodes := fmt.Sprintf( `NODES='[ { "name": "%s", "lspdPrivateKey": "%x", "token": "hello", "host": "host:port",`+ ` "publicChannelAmount": "1000183", "channelAmount": "100000", "channelPrivate": false,`+ - ` "targetConf": "6", "minHtlcMsat": "600", "baseFeeMsat": "1000", "feeRate": "0.000001",`+ + ` "targetConf": "6", "minConfs": "1", "minHtlcMsat": "600", "baseFeeMsat": "1000", "feeRate": "0.000001",`+ ` "timeLockDelta": "144", "channelFeePermyriad": "40", "channelMinimumFeeMsat": "2000000",`+ ` "additionalChannelCapacity": "100000", "maxInactiveDuration": "3888000", %s}]'`, name, @@ -112,6 +112,7 @@ func newLspd(h *lntest.TestHarness, name string, lnd *string, cln *string, envEx fmt.Sprintf("LISTEN_ADDRESS=%s", grpcAddress), fmt.Sprintf("USE_MEMPOOL_FEE_ESTIMATION=true"), fmt.Sprintf("MEMPOOL_API_BASE_URL=https://mempool.space/api/v1/"), + fmt.Sprintf("MEMPOOL_PRIORITY=economy"), } env = append(env, envExt...) diff --git a/main.go b/main.go index e55c4f3..975cff3 100644 --- a/main.go +++ b/main.go @@ -6,9 +6,11 @@ import ( "log" "os" "os/signal" + "strings" "sync" "syscall" + "github.com/breez/lspd/chain" "github.com/breez/lspd/mempool" "github.com/btcsuite/btcd/btcec/v2" ) @@ -42,7 +44,22 @@ func main() { log.Fatalf("failed to initialize mempool client: %v", err) } - log.Printf("using mempool api for fee estimation: %v", mempoolUrl) + envFeeStrategy := os.Getenv("MEMPOOL_PRIORITY") + switch strings.ToLower(envFeeStrategy) { + case "minimum": + feeStrategy = chain.FeeStrategyMinimum + case "economy": + feeStrategy = chain.FeeStrategyEconomy + case "hour": + feeStrategy = chain.FeeStrategyHour + case "halfhour": + feeStrategy = chain.FeeStrategyHalfHour + case "fastest": + feeStrategy = chain.FeeStrategyFastest + default: + feeStrategy = chain.FeeStrategyEconomy + } + log.Printf("using mempool api for fee estimation: %v, fee strategy: %v:%v", mempoolUrl, envFeeStrategy, feeStrategy) } var interceptors []HtlcInterceptor diff --git a/sample.env b/sample.env index 4a2d7ad..f266c14 100644 --- a/sample.env +++ b/sample.env @@ -36,6 +36,11 @@ CHANNELMISMATCH_NOTIFICATION_FROM="Name4 " USE_MEMPOOL_FEE_ESTIMATION=true MEMPOOL_API_BASE_URL=https://mempool.space/api/v1/ +# Priority to use for opening channels when using the mempool api. +# Valid options are: fastest, halfhour, hour, economy, minimum +# Defaults to economy +MEMPOOL_PRIORITY=economy + # lspd can be connected to multiple nodes at once. The NODES variable takes an # array of nodes. Each node is either a cln or an lnd node and should have the # corresponding "cln" or "lnd" key set. @@ -53,4 +58,4 @@ MEMPOOL_API_BASE_URL=https://mempool.space/api/v1/ # from the app to lspd. # # For other specific settings see the fields in `config.go` NodeConfig struct. -NODES='[ { "name": "", "nodePubkey": "", "lspdPrivateKey": "", "token": "", "host": "", "publicChannelAmount": "1000183", "channelAmount": "100000", "channelPrivate": false, "targetConf": "6", "minHtlcMsat": "600", "baseFeeMsat": "1000", "feeRate": "0.000001", "timeLockDelta": "144", "channelFeePermyriad": "40", "channelMinimumFeeMsat": "2000000", "additionalChannelCapacity": "100000", "maxInactiveDuration": "3888000", "lnd": { "address": "", "cert": "", "macaroon": "" } }, { "name": "", "nodePubkey": "", "lspdPrivateKey": "", "token": "", "host": "", "publicChannelAmount": "1000183", "channelAmount": "100000", "channelPrivate": false, "targetConf": "6", "minHtlcMsat": "600", "baseFeeMsat": "1000", "feeRate": "0.000001", "timeLockDelta": "144", "channelFeePermyriad": "40", "channelMinimumFeeMsat": "2000000", "additionalChannelCapacity": "100000", "maxInactiveDuration": "3888000", "cln": { "pluginAddress": "
", "socketPath": "" } } ]' +NODES='[ { "name": "", "nodePubkey": "", "lspdPrivateKey": "", "token": "", "host": "", "publicChannelAmount": "1000183", "channelAmount": "100000", "channelPrivate": false, "targetConf": "6", "minConfs": "6", "minHtlcMsat": "600", "baseFeeMsat": "1000", "feeRate": "0.000001", "timeLockDelta": "144", "channelFeePermyriad": "40", "channelMinimumFeeMsat": "2000000", "additionalChannelCapacity": "100000", "maxInactiveDuration": "3888000", "lnd": { "address": "", "cert": "", "macaroon": "" } }, { "name": "", "nodePubkey": "", "lspdPrivateKey": "", "token": "", "host": "", "publicChannelAmount": "1000183", "channelAmount": "100000", "channelPrivate": false, "targetConf": "6", "minConfs": "6", "minHtlcMsat": "600", "baseFeeMsat": "1000", "feeRate": "0.000001", "timeLockDelta": "144", "channelFeePermyriad": "40", "channelMinimumFeeMsat": "2000000", "additionalChannelCapacity": "100000", "maxInactiveDuration": "3888000", "cln": { "pluginAddress": "
", "socketPath": "" } } ]' From ef3a001d54e1edff26b0927c2994b5f2ebd7bb1d Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Mon, 13 Feb 2023 09:39:51 +0100 Subject: [PATCH 152/214] add tag field to registerpayment proto --- rpc/genproto.sh | 4 +- rpc/lspd.pb.go | 1505 ++++++++++++++++++++++++------------------- rpc/lspd.proto | 2 + rpc/lspd_grpc.pb.go | 213 ++++++ server.go | 1 + 5 files changed, 1059 insertions(+), 666 deletions(-) create mode 100644 rpc/lspd_grpc.pb.go diff --git a/rpc/genproto.sh b/rpc/genproto.sh index 6a2b80a..c3bedff 100755 --- a/rpc/genproto.sh +++ b/rpc/genproto.sh @@ -1,2 +1,4 @@ #!/bin/bash -protoc -I . lspd.proto --go_out=plugins=grpc:. +SCRIPTDIR=$(dirname $0) + +protoc --go_out=$SCRIPTDIR --go_opt=paths=source_relative --go-grpc_out=$SCRIPTDIR --go-grpc_opt=paths=source_relative -I=$SCRIPTDIR $SCRIPTDIR/*.proto \ No newline at end of file diff --git a/rpc/lspd.pb.go b/rpc/lspd.pb.go index be1c80e..aa694e9 100644 --- a/rpc/lspd.pb.go +++ b/rpc/lspd.pb.go @@ -1,70 +1,78 @@ // Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.28.1 +// protoc v3.21.12 // source: lspd.proto package lspd import ( - context "context" - fmt "fmt" - proto "github.com/golang/protobuf/proto" - grpc "google.golang.org/grpc" - codes "google.golang.org/grpc/codes" - status "google.golang.org/grpc/status" - math "math" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" ) -// Reference imports to suppress errors if they are not otherwise used. -var _ = proto.Marshal -var _ = fmt.Errorf -var _ = math.Inf - -// This is a compile-time assertion to ensure that this generated file -// is compatible with the proto package it is being compiled against. -// A compilation error at this line likely means your copy of the -// proto package needs to be updated. -const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) type ChannelInformationRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + /// The identity pubkey of the Lightning node - Pubkey string `protobuf:"bytes,1,opt,name=pubkey,proto3" json:"pubkey,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + Pubkey string `protobuf:"bytes,1,opt,name=pubkey,proto3" json:"pubkey,omitempty"` } -func (m *ChannelInformationRequest) Reset() { *m = ChannelInformationRequest{} } -func (m *ChannelInformationRequest) String() string { return proto.CompactTextString(m) } -func (*ChannelInformationRequest) ProtoMessage() {} +func (x *ChannelInformationRequest) Reset() { + *x = ChannelInformationRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_lspd_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ChannelInformationRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ChannelInformationRequest) ProtoMessage() {} + +func (x *ChannelInformationRequest) ProtoReflect() protoreflect.Message { + mi := &file_lspd_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ChannelInformationRequest.ProtoReflect.Descriptor instead. func (*ChannelInformationRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_c69a0f5a734bca26, []int{0} + return file_lspd_proto_rawDescGZIP(), []int{0} } -func (m *ChannelInformationRequest) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_ChannelInformationRequest.Unmarshal(m, b) -} -func (m *ChannelInformationRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_ChannelInformationRequest.Marshal(b, m, deterministic) -} -func (m *ChannelInformationRequest) XXX_Merge(src proto.Message) { - xxx_messageInfo_ChannelInformationRequest.Merge(m, src) -} -func (m *ChannelInformationRequest) XXX_Size() int { - return xxx_messageInfo_ChannelInformationRequest.Size(m) -} -func (m *ChannelInformationRequest) XXX_DiscardUnknown() { - xxx_messageInfo_ChannelInformationRequest.DiscardUnknown(m) -} - -var xxx_messageInfo_ChannelInformationRequest proto.InternalMessageInfo - -func (m *ChannelInformationRequest) GetPubkey() string { - if m != nil { - return m.Pubkey +func (x *ChannelInformationRequest) GetPubkey() string { + if x != nil { + return x.Pubkey } return "" } type ChannelInformationReply struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + /// The name of of lsp Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` /// The identity pubkey of the Lightning node @@ -90,822 +98,989 @@ type ChannelInformationReply struct { ChannelFeePermyriad int64 `protobuf:"varint,10,opt,name=channel_fee_permyriad,json=channelFeePermyriad,proto3" json:"channel_fee_permyriad,omitempty"` LspPubkey []byte `protobuf:"bytes,11,opt,name=lsp_pubkey,json=lspPubkey,proto3" json:"lsp_pubkey,omitempty"` // The channel can be closed if not used this duration in seconds. - MaxInactiveDuration int64 `protobuf:"varint,12,opt,name=max_inactive_duration,json=maxInactiveDuration,proto3" json:"max_inactive_duration,omitempty"` - ChannelMinimumFeeMsat int64 `protobuf:"varint,13,opt,name=channel_minimum_fee_msat,json=channelMinimumFeeMsat,proto3" json:"channel_minimum_fee_msat,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + MaxInactiveDuration int64 `protobuf:"varint,12,opt,name=max_inactive_duration,json=maxInactiveDuration,proto3" json:"max_inactive_duration,omitempty"` + ChannelMinimumFeeMsat int64 `protobuf:"varint,13,opt,name=channel_minimum_fee_msat,json=channelMinimumFeeMsat,proto3" json:"channel_minimum_fee_msat,omitempty"` } -func (m *ChannelInformationReply) Reset() { *m = ChannelInformationReply{} } -func (m *ChannelInformationReply) String() string { return proto.CompactTextString(m) } -func (*ChannelInformationReply) ProtoMessage() {} +func (x *ChannelInformationReply) Reset() { + *x = ChannelInformationReply{} + if protoimpl.UnsafeEnabled { + mi := &file_lspd_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ChannelInformationReply) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ChannelInformationReply) ProtoMessage() {} + +func (x *ChannelInformationReply) ProtoReflect() protoreflect.Message { + mi := &file_lspd_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ChannelInformationReply.ProtoReflect.Descriptor instead. func (*ChannelInformationReply) Descriptor() ([]byte, []int) { - return fileDescriptor_c69a0f5a734bca26, []int{1} + return file_lspd_proto_rawDescGZIP(), []int{1} } -func (m *ChannelInformationReply) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_ChannelInformationReply.Unmarshal(m, b) -} -func (m *ChannelInformationReply) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_ChannelInformationReply.Marshal(b, m, deterministic) -} -func (m *ChannelInformationReply) XXX_Merge(src proto.Message) { - xxx_messageInfo_ChannelInformationReply.Merge(m, src) -} -func (m *ChannelInformationReply) XXX_Size() int { - return xxx_messageInfo_ChannelInformationReply.Size(m) -} -func (m *ChannelInformationReply) XXX_DiscardUnknown() { - xxx_messageInfo_ChannelInformationReply.DiscardUnknown(m) -} - -var xxx_messageInfo_ChannelInformationReply proto.InternalMessageInfo - -func (m *ChannelInformationReply) GetName() string { - if m != nil { - return m.Name +func (x *ChannelInformationReply) GetName() string { + if x != nil { + return x.Name } return "" } -func (m *ChannelInformationReply) GetPubkey() string { - if m != nil { - return m.Pubkey +func (x *ChannelInformationReply) GetPubkey() string { + if x != nil { + return x.Pubkey } return "" } -func (m *ChannelInformationReply) GetHost() string { - if m != nil { - return m.Host +func (x *ChannelInformationReply) GetHost() string { + if x != nil { + return x.Host } return "" } -func (m *ChannelInformationReply) GetChannelCapacity() int64 { - if m != nil { - return m.ChannelCapacity +func (x *ChannelInformationReply) GetChannelCapacity() int64 { + if x != nil { + return x.ChannelCapacity } return 0 } -func (m *ChannelInformationReply) GetTargetConf() int32 { - if m != nil { - return m.TargetConf +func (x *ChannelInformationReply) GetTargetConf() int32 { + if x != nil { + return x.TargetConf } return 0 } -func (m *ChannelInformationReply) GetBaseFeeMsat() int64 { - if m != nil { - return m.BaseFeeMsat +func (x *ChannelInformationReply) GetBaseFeeMsat() int64 { + if x != nil { + return x.BaseFeeMsat } return 0 } -func (m *ChannelInformationReply) GetFeeRate() float64 { - if m != nil { - return m.FeeRate +func (x *ChannelInformationReply) GetFeeRate() float64 { + if x != nil { + return x.FeeRate } return 0 } -func (m *ChannelInformationReply) GetTimeLockDelta() uint32 { - if m != nil { - return m.TimeLockDelta +func (x *ChannelInformationReply) GetTimeLockDelta() uint32 { + if x != nil { + return x.TimeLockDelta } return 0 } -func (m *ChannelInformationReply) GetMinHtlcMsat() int64 { - if m != nil { - return m.MinHtlcMsat +func (x *ChannelInformationReply) GetMinHtlcMsat() int64 { + if x != nil { + return x.MinHtlcMsat } return 0 } -func (m *ChannelInformationReply) GetChannelFeePermyriad() int64 { - if m != nil { - return m.ChannelFeePermyriad +func (x *ChannelInformationReply) GetChannelFeePermyriad() int64 { + if x != nil { + return x.ChannelFeePermyriad } return 0 } -func (m *ChannelInformationReply) GetLspPubkey() []byte { - if m != nil { - return m.LspPubkey +func (x *ChannelInformationReply) GetLspPubkey() []byte { + if x != nil { + return x.LspPubkey } return nil } -func (m *ChannelInformationReply) GetMaxInactiveDuration() int64 { - if m != nil { - return m.MaxInactiveDuration +func (x *ChannelInformationReply) GetMaxInactiveDuration() int64 { + if x != nil { + return x.MaxInactiveDuration } return 0 } -func (m *ChannelInformationReply) GetChannelMinimumFeeMsat() int64 { - if m != nil { - return m.ChannelMinimumFeeMsat +func (x *ChannelInformationReply) GetChannelMinimumFeeMsat() int64 { + if x != nil { + return x.ChannelMinimumFeeMsat } return 0 } type OpenChannelRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + /// The identity pubkey of the Lightning node - Pubkey string `protobuf:"bytes,1,opt,name=pubkey,proto3" json:"pubkey,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + Pubkey string `protobuf:"bytes,1,opt,name=pubkey,proto3" json:"pubkey,omitempty"` } -func (m *OpenChannelRequest) Reset() { *m = OpenChannelRequest{} } -func (m *OpenChannelRequest) String() string { return proto.CompactTextString(m) } -func (*OpenChannelRequest) ProtoMessage() {} +func (x *OpenChannelRequest) Reset() { + *x = OpenChannelRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_lspd_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *OpenChannelRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*OpenChannelRequest) ProtoMessage() {} + +func (x *OpenChannelRequest) ProtoReflect() protoreflect.Message { + mi := &file_lspd_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use OpenChannelRequest.ProtoReflect.Descriptor instead. func (*OpenChannelRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_c69a0f5a734bca26, []int{2} + return file_lspd_proto_rawDescGZIP(), []int{2} } -func (m *OpenChannelRequest) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_OpenChannelRequest.Unmarshal(m, b) -} -func (m *OpenChannelRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_OpenChannelRequest.Marshal(b, m, deterministic) -} -func (m *OpenChannelRequest) XXX_Merge(src proto.Message) { - xxx_messageInfo_OpenChannelRequest.Merge(m, src) -} -func (m *OpenChannelRequest) XXX_Size() int { - return xxx_messageInfo_OpenChannelRequest.Size(m) -} -func (m *OpenChannelRequest) XXX_DiscardUnknown() { - xxx_messageInfo_OpenChannelRequest.DiscardUnknown(m) -} - -var xxx_messageInfo_OpenChannelRequest proto.InternalMessageInfo - -func (m *OpenChannelRequest) GetPubkey() string { - if m != nil { - return m.Pubkey +func (x *OpenChannelRequest) GetPubkey() string { + if x != nil { + return x.Pubkey } return "" } type OpenChannelReply struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + /// The transaction hash TxHash string `protobuf:"bytes,1,opt,name=tx_hash,proto3" json:"tx_hash,omitempty"` /// The output index - OutputIndex uint32 `protobuf:"varint,2,opt,name=output_index,proto3" json:"output_index,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + OutputIndex uint32 `protobuf:"varint,2,opt,name=output_index,proto3" json:"output_index,omitempty"` } -func (m *OpenChannelReply) Reset() { *m = OpenChannelReply{} } -func (m *OpenChannelReply) String() string { return proto.CompactTextString(m) } -func (*OpenChannelReply) ProtoMessage() {} +func (x *OpenChannelReply) Reset() { + *x = OpenChannelReply{} + if protoimpl.UnsafeEnabled { + mi := &file_lspd_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *OpenChannelReply) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*OpenChannelReply) ProtoMessage() {} + +func (x *OpenChannelReply) ProtoReflect() protoreflect.Message { + mi := &file_lspd_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use OpenChannelReply.ProtoReflect.Descriptor instead. func (*OpenChannelReply) Descriptor() ([]byte, []int) { - return fileDescriptor_c69a0f5a734bca26, []int{3} + return file_lspd_proto_rawDescGZIP(), []int{3} } -func (m *OpenChannelReply) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_OpenChannelReply.Unmarshal(m, b) -} -func (m *OpenChannelReply) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_OpenChannelReply.Marshal(b, m, deterministic) -} -func (m *OpenChannelReply) XXX_Merge(src proto.Message) { - xxx_messageInfo_OpenChannelReply.Merge(m, src) -} -func (m *OpenChannelReply) XXX_Size() int { - return xxx_messageInfo_OpenChannelReply.Size(m) -} -func (m *OpenChannelReply) XXX_DiscardUnknown() { - xxx_messageInfo_OpenChannelReply.DiscardUnknown(m) -} - -var xxx_messageInfo_OpenChannelReply proto.InternalMessageInfo - -func (m *OpenChannelReply) GetTxHash() string { - if m != nil { - return m.TxHash +func (x *OpenChannelReply) GetTxHash() string { + if x != nil { + return x.TxHash } return "" } -func (m *OpenChannelReply) GetOutputIndex() uint32 { - if m != nil { - return m.OutputIndex +func (x *OpenChannelReply) GetOutputIndex() uint32 { + if x != nil { + return x.OutputIndex } return 0 } type RegisterPaymentRequest struct { - Blob []byte `protobuf:"bytes,3,opt,name=blob,proto3" json:"blob,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Blob []byte `protobuf:"bytes,3,opt,name=blob,proto3" json:"blob,omitempty"` } -func (m *RegisterPaymentRequest) Reset() { *m = RegisterPaymentRequest{} } -func (m *RegisterPaymentRequest) String() string { return proto.CompactTextString(m) } -func (*RegisterPaymentRequest) ProtoMessage() {} +func (x *RegisterPaymentRequest) Reset() { + *x = RegisterPaymentRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_lspd_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RegisterPaymentRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RegisterPaymentRequest) ProtoMessage() {} + +func (x *RegisterPaymentRequest) ProtoReflect() protoreflect.Message { + mi := &file_lspd_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RegisterPaymentRequest.ProtoReflect.Descriptor instead. func (*RegisterPaymentRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_c69a0f5a734bca26, []int{4} + return file_lspd_proto_rawDescGZIP(), []int{4} } -func (m *RegisterPaymentRequest) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_RegisterPaymentRequest.Unmarshal(m, b) -} -func (m *RegisterPaymentRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_RegisterPaymentRequest.Marshal(b, m, deterministic) -} -func (m *RegisterPaymentRequest) XXX_Merge(src proto.Message) { - xxx_messageInfo_RegisterPaymentRequest.Merge(m, src) -} -func (m *RegisterPaymentRequest) XXX_Size() int { - return xxx_messageInfo_RegisterPaymentRequest.Size(m) -} -func (m *RegisterPaymentRequest) XXX_DiscardUnknown() { - xxx_messageInfo_RegisterPaymentRequest.DiscardUnknown(m) -} - -var xxx_messageInfo_RegisterPaymentRequest proto.InternalMessageInfo - -func (m *RegisterPaymentRequest) GetBlob() []byte { - if m != nil { - return m.Blob +func (x *RegisterPaymentRequest) GetBlob() []byte { + if x != nil { + return x.Blob } return nil } type RegisterPaymentReply struct { - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields } -func (m *RegisterPaymentReply) Reset() { *m = RegisterPaymentReply{} } -func (m *RegisterPaymentReply) String() string { return proto.CompactTextString(m) } -func (*RegisterPaymentReply) ProtoMessage() {} +func (x *RegisterPaymentReply) Reset() { + *x = RegisterPaymentReply{} + if protoimpl.UnsafeEnabled { + mi := &file_lspd_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RegisterPaymentReply) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RegisterPaymentReply) ProtoMessage() {} + +func (x *RegisterPaymentReply) ProtoReflect() protoreflect.Message { + mi := &file_lspd_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RegisterPaymentReply.ProtoReflect.Descriptor instead. func (*RegisterPaymentReply) Descriptor() ([]byte, []int) { - return fileDescriptor_c69a0f5a734bca26, []int{5} + return file_lspd_proto_rawDescGZIP(), []int{5} } -func (m *RegisterPaymentReply) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_RegisterPaymentReply.Unmarshal(m, b) -} -func (m *RegisterPaymentReply) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_RegisterPaymentReply.Marshal(b, m, deterministic) -} -func (m *RegisterPaymentReply) XXX_Merge(src proto.Message) { - xxx_messageInfo_RegisterPaymentReply.Merge(m, src) -} -func (m *RegisterPaymentReply) XXX_Size() int { - return xxx_messageInfo_RegisterPaymentReply.Size(m) -} -func (m *RegisterPaymentReply) XXX_DiscardUnknown() { - xxx_messageInfo_RegisterPaymentReply.DiscardUnknown(m) -} - -var xxx_messageInfo_RegisterPaymentReply proto.InternalMessageInfo - type PaymentInformation struct { - PaymentHash []byte `protobuf:"bytes,1,opt,name=payment_hash,json=paymentHash,proto3" json:"payment_hash,omitempty"` - PaymentSecret []byte `protobuf:"bytes,2,opt,name=payment_secret,json=paymentSecret,proto3" json:"payment_secret,omitempty"` - Destination []byte `protobuf:"bytes,3,opt,name=destination,proto3" json:"destination,omitempty"` - IncomingAmountMsat int64 `protobuf:"varint,4,opt,name=incoming_amount_msat,json=incomingAmountMsat,proto3" json:"incoming_amount_msat,omitempty"` - OutgoingAmountMsat int64 `protobuf:"varint,5,opt,name=outgoing_amount_msat,json=outgoingAmountMsat,proto3" json:"outgoing_amount_msat,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + PaymentHash []byte `protobuf:"bytes,1,opt,name=payment_hash,json=paymentHash,proto3" json:"payment_hash,omitempty"` + PaymentSecret []byte `protobuf:"bytes,2,opt,name=payment_secret,json=paymentSecret,proto3" json:"payment_secret,omitempty"` + Destination []byte `protobuf:"bytes,3,opt,name=destination,proto3" json:"destination,omitempty"` + IncomingAmountMsat int64 `protobuf:"varint,4,opt,name=incoming_amount_msat,json=incomingAmountMsat,proto3" json:"incoming_amount_msat,omitempty"` + OutgoingAmountMsat int64 `protobuf:"varint,5,opt,name=outgoing_amount_msat,json=outgoingAmountMsat,proto3" json:"outgoing_amount_msat,omitempty"` + Tag string `protobuf:"bytes,6,opt,name=tag,proto3" json:"tag,omitempty"` } -func (m *PaymentInformation) Reset() { *m = PaymentInformation{} } -func (m *PaymentInformation) String() string { return proto.CompactTextString(m) } -func (*PaymentInformation) ProtoMessage() {} +func (x *PaymentInformation) Reset() { + *x = PaymentInformation{} + if protoimpl.UnsafeEnabled { + mi := &file_lspd_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *PaymentInformation) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PaymentInformation) ProtoMessage() {} + +func (x *PaymentInformation) ProtoReflect() protoreflect.Message { + mi := &file_lspd_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PaymentInformation.ProtoReflect.Descriptor instead. func (*PaymentInformation) Descriptor() ([]byte, []int) { - return fileDescriptor_c69a0f5a734bca26, []int{6} + return file_lspd_proto_rawDescGZIP(), []int{6} } -func (m *PaymentInformation) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_PaymentInformation.Unmarshal(m, b) -} -func (m *PaymentInformation) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_PaymentInformation.Marshal(b, m, deterministic) -} -func (m *PaymentInformation) XXX_Merge(src proto.Message) { - xxx_messageInfo_PaymentInformation.Merge(m, src) -} -func (m *PaymentInformation) XXX_Size() int { - return xxx_messageInfo_PaymentInformation.Size(m) -} -func (m *PaymentInformation) XXX_DiscardUnknown() { - xxx_messageInfo_PaymentInformation.DiscardUnknown(m) -} - -var xxx_messageInfo_PaymentInformation proto.InternalMessageInfo - -func (m *PaymentInformation) GetPaymentHash() []byte { - if m != nil { - return m.PaymentHash +func (x *PaymentInformation) GetPaymentHash() []byte { + if x != nil { + return x.PaymentHash } return nil } -func (m *PaymentInformation) GetPaymentSecret() []byte { - if m != nil { - return m.PaymentSecret +func (x *PaymentInformation) GetPaymentSecret() []byte { + if x != nil { + return x.PaymentSecret } return nil } -func (m *PaymentInformation) GetDestination() []byte { - if m != nil { - return m.Destination +func (x *PaymentInformation) GetDestination() []byte { + if x != nil { + return x.Destination } return nil } -func (m *PaymentInformation) GetIncomingAmountMsat() int64 { - if m != nil { - return m.IncomingAmountMsat +func (x *PaymentInformation) GetIncomingAmountMsat() int64 { + if x != nil { + return x.IncomingAmountMsat } return 0 } -func (m *PaymentInformation) GetOutgoingAmountMsat() int64 { - if m != nil { - return m.OutgoingAmountMsat +func (x *PaymentInformation) GetOutgoingAmountMsat() int64 { + if x != nil { + return x.OutgoingAmountMsat } return 0 } +func (x *PaymentInformation) GetTag() string { + if x != nil { + return x.Tag + } + return "" +} + type Encrypted struct { - Data []byte `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Data []byte `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"` } -func (m *Encrypted) Reset() { *m = Encrypted{} } -func (m *Encrypted) String() string { return proto.CompactTextString(m) } -func (*Encrypted) ProtoMessage() {} +func (x *Encrypted) Reset() { + *x = Encrypted{} + if protoimpl.UnsafeEnabled { + mi := &file_lspd_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Encrypted) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Encrypted) ProtoMessage() {} + +func (x *Encrypted) ProtoReflect() protoreflect.Message { + mi := &file_lspd_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Encrypted.ProtoReflect.Descriptor instead. func (*Encrypted) Descriptor() ([]byte, []int) { - return fileDescriptor_c69a0f5a734bca26, []int{7} + return file_lspd_proto_rawDescGZIP(), []int{7} } -func (m *Encrypted) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_Encrypted.Unmarshal(m, b) -} -func (m *Encrypted) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_Encrypted.Marshal(b, m, deterministic) -} -func (m *Encrypted) XXX_Merge(src proto.Message) { - xxx_messageInfo_Encrypted.Merge(m, src) -} -func (m *Encrypted) XXX_Size() int { - return xxx_messageInfo_Encrypted.Size(m) -} -func (m *Encrypted) XXX_DiscardUnknown() { - xxx_messageInfo_Encrypted.DiscardUnknown(m) -} - -var xxx_messageInfo_Encrypted proto.InternalMessageInfo - -func (m *Encrypted) GetData() []byte { - if m != nil { - return m.Data +func (x *Encrypted) GetData() []byte { + if x != nil { + return x.Data } return nil } type Signed struct { - Data []byte `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"` - Pubkey []byte `protobuf:"bytes,2,opt,name=pubkey,proto3" json:"pubkey,omitempty"` - Signature []byte `protobuf:"bytes,3,opt,name=signature,proto3" json:"signature,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Data []byte `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"` + Pubkey []byte `protobuf:"bytes,2,opt,name=pubkey,proto3" json:"pubkey,omitempty"` + Signature []byte `protobuf:"bytes,3,opt,name=signature,proto3" json:"signature,omitempty"` } -func (m *Signed) Reset() { *m = Signed{} } -func (m *Signed) String() string { return proto.CompactTextString(m) } -func (*Signed) ProtoMessage() {} +func (x *Signed) Reset() { + *x = Signed{} + if protoimpl.UnsafeEnabled { + mi := &file_lspd_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Signed) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Signed) ProtoMessage() {} + +func (x *Signed) ProtoReflect() protoreflect.Message { + mi := &file_lspd_proto_msgTypes[8] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Signed.ProtoReflect.Descriptor instead. func (*Signed) Descriptor() ([]byte, []int) { - return fileDescriptor_c69a0f5a734bca26, []int{8} + return file_lspd_proto_rawDescGZIP(), []int{8} } -func (m *Signed) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_Signed.Unmarshal(m, b) -} -func (m *Signed) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_Signed.Marshal(b, m, deterministic) -} -func (m *Signed) XXX_Merge(src proto.Message) { - xxx_messageInfo_Signed.Merge(m, src) -} -func (m *Signed) XXX_Size() int { - return xxx_messageInfo_Signed.Size(m) -} -func (m *Signed) XXX_DiscardUnknown() { - xxx_messageInfo_Signed.DiscardUnknown(m) -} - -var xxx_messageInfo_Signed proto.InternalMessageInfo - -func (m *Signed) GetData() []byte { - if m != nil { - return m.Data +func (x *Signed) GetData() []byte { + if x != nil { + return x.Data } return nil } -func (m *Signed) GetPubkey() []byte { - if m != nil { - return m.Pubkey +func (x *Signed) GetPubkey() []byte { + if x != nil { + return x.Pubkey } return nil } -func (m *Signed) GetSignature() []byte { - if m != nil { - return m.Signature +func (x *Signed) GetSignature() []byte { + if x != nil { + return x.Signature } return nil } type CheckChannelsRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + EncryptPubkey []byte `protobuf:"bytes,1,opt,name=encrypt_pubkey,json=encryptPubkey,proto3" json:"encrypt_pubkey,omitempty"` FakeChannels map[string]uint64 `protobuf:"bytes,2,rep,name=fake_channels,json=fakeChannels,proto3" json:"fake_channels,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"varint,2,opt,name=value,proto3"` WaitingCloseChannels map[string]uint64 `protobuf:"bytes,3,rep,name=waiting_close_channels,json=waitingCloseChannels,proto3" json:"waiting_close_channels,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"varint,2,opt,name=value,proto3"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` } -func (m *CheckChannelsRequest) Reset() { *m = CheckChannelsRequest{} } -func (m *CheckChannelsRequest) String() string { return proto.CompactTextString(m) } -func (*CheckChannelsRequest) ProtoMessage() {} +func (x *CheckChannelsRequest) Reset() { + *x = CheckChannelsRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_lspd_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CheckChannelsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CheckChannelsRequest) ProtoMessage() {} + +func (x *CheckChannelsRequest) ProtoReflect() protoreflect.Message { + mi := &file_lspd_proto_msgTypes[9] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CheckChannelsRequest.ProtoReflect.Descriptor instead. func (*CheckChannelsRequest) Descriptor() ([]byte, []int) { - return fileDescriptor_c69a0f5a734bca26, []int{9} + return file_lspd_proto_rawDescGZIP(), []int{9} } -func (m *CheckChannelsRequest) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_CheckChannelsRequest.Unmarshal(m, b) -} -func (m *CheckChannelsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_CheckChannelsRequest.Marshal(b, m, deterministic) -} -func (m *CheckChannelsRequest) XXX_Merge(src proto.Message) { - xxx_messageInfo_CheckChannelsRequest.Merge(m, src) -} -func (m *CheckChannelsRequest) XXX_Size() int { - return xxx_messageInfo_CheckChannelsRequest.Size(m) -} -func (m *CheckChannelsRequest) XXX_DiscardUnknown() { - xxx_messageInfo_CheckChannelsRequest.DiscardUnknown(m) -} - -var xxx_messageInfo_CheckChannelsRequest proto.InternalMessageInfo - -func (m *CheckChannelsRequest) GetEncryptPubkey() []byte { - if m != nil { - return m.EncryptPubkey +func (x *CheckChannelsRequest) GetEncryptPubkey() []byte { + if x != nil { + return x.EncryptPubkey } return nil } -func (m *CheckChannelsRequest) GetFakeChannels() map[string]uint64 { - if m != nil { - return m.FakeChannels +func (x *CheckChannelsRequest) GetFakeChannels() map[string]uint64 { + if x != nil { + return x.FakeChannels } return nil } -func (m *CheckChannelsRequest) GetWaitingCloseChannels() map[string]uint64 { - if m != nil { - return m.WaitingCloseChannels +func (x *CheckChannelsRequest) GetWaitingCloseChannels() map[string]uint64 { + if x != nil { + return x.WaitingCloseChannels } return nil } type CheckChannelsReply struct { - NotFakeChannels map[string]uint64 `protobuf:"bytes,1,rep,name=not_fake_channels,json=notFakeChannels,proto3" json:"not_fake_channels,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"varint,2,opt,name=value,proto3"` - ClosedChannels map[string]uint64 `protobuf:"bytes,2,rep,name=closed_channels,json=closedChannels,proto3" json:"closed_channels,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"varint,2,opt,name=value,proto3"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + NotFakeChannels map[string]uint64 `protobuf:"bytes,1,rep,name=not_fake_channels,json=notFakeChannels,proto3" json:"not_fake_channels,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"varint,2,opt,name=value,proto3"` + ClosedChannels map[string]uint64 `protobuf:"bytes,2,rep,name=closed_channels,json=closedChannels,proto3" json:"closed_channels,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"varint,2,opt,name=value,proto3"` } -func (m *CheckChannelsReply) Reset() { *m = CheckChannelsReply{} } -func (m *CheckChannelsReply) String() string { return proto.CompactTextString(m) } -func (*CheckChannelsReply) ProtoMessage() {} +func (x *CheckChannelsReply) Reset() { + *x = CheckChannelsReply{} + if protoimpl.UnsafeEnabled { + mi := &file_lspd_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CheckChannelsReply) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CheckChannelsReply) ProtoMessage() {} + +func (x *CheckChannelsReply) ProtoReflect() protoreflect.Message { + mi := &file_lspd_proto_msgTypes[10] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CheckChannelsReply.ProtoReflect.Descriptor instead. func (*CheckChannelsReply) Descriptor() ([]byte, []int) { - return fileDescriptor_c69a0f5a734bca26, []int{10} + return file_lspd_proto_rawDescGZIP(), []int{10} } -func (m *CheckChannelsReply) XXX_Unmarshal(b []byte) error { - return xxx_messageInfo_CheckChannelsReply.Unmarshal(m, b) -} -func (m *CheckChannelsReply) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - return xxx_messageInfo_CheckChannelsReply.Marshal(b, m, deterministic) -} -func (m *CheckChannelsReply) XXX_Merge(src proto.Message) { - xxx_messageInfo_CheckChannelsReply.Merge(m, src) -} -func (m *CheckChannelsReply) XXX_Size() int { - return xxx_messageInfo_CheckChannelsReply.Size(m) -} -func (m *CheckChannelsReply) XXX_DiscardUnknown() { - xxx_messageInfo_CheckChannelsReply.DiscardUnknown(m) -} - -var xxx_messageInfo_CheckChannelsReply proto.InternalMessageInfo - -func (m *CheckChannelsReply) GetNotFakeChannels() map[string]uint64 { - if m != nil { - return m.NotFakeChannels +func (x *CheckChannelsReply) GetNotFakeChannels() map[string]uint64 { + if x != nil { + return x.NotFakeChannels } return nil } -func (m *CheckChannelsReply) GetClosedChannels() map[string]uint64 { - if m != nil { - return m.ClosedChannels +func (x *CheckChannelsReply) GetClosedChannels() map[string]uint64 { + if x != nil { + return x.ClosedChannels } return nil } -func init() { - proto.RegisterType((*ChannelInformationRequest)(nil), "lspd.ChannelInformationRequest") - proto.RegisterType((*ChannelInformationReply)(nil), "lspd.ChannelInformationReply") - proto.RegisterType((*OpenChannelRequest)(nil), "lspd.OpenChannelRequest") - proto.RegisterType((*OpenChannelReply)(nil), "lspd.OpenChannelReply") - proto.RegisterType((*RegisterPaymentRequest)(nil), "lspd.RegisterPaymentRequest") - proto.RegisterType((*RegisterPaymentReply)(nil), "lspd.RegisterPaymentReply") - proto.RegisterType((*PaymentInformation)(nil), "lspd.PaymentInformation") - proto.RegisterType((*Encrypted)(nil), "lspd.Encrypted") - proto.RegisterType((*Signed)(nil), "lspd.Signed") - proto.RegisterType((*CheckChannelsRequest)(nil), "lspd.CheckChannelsRequest") - proto.RegisterMapType((map[string]uint64)(nil), "lspd.CheckChannelsRequest.FakeChannelsEntry") - proto.RegisterMapType((map[string]uint64)(nil), "lspd.CheckChannelsRequest.WaitingCloseChannelsEntry") - proto.RegisterType((*CheckChannelsReply)(nil), "lspd.CheckChannelsReply") - proto.RegisterMapType((map[string]uint64)(nil), "lspd.CheckChannelsReply.ClosedChannelsEntry") - proto.RegisterMapType((map[string]uint64)(nil), "lspd.CheckChannelsReply.NotFakeChannelsEntry") +var File_lspd_proto protoreflect.FileDescriptor + +var file_lspd_proto_rawDesc = []byte{ + 0x0a, 0x0a, 0x6c, 0x73, 0x70, 0x64, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x04, 0x6c, 0x73, + 0x70, 0x64, 0x22, 0x33, 0x0a, 0x19, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x49, 0x6e, 0x66, + 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x16, 0x0a, 0x06, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x06, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x22, 0xf9, 0x03, 0x0a, 0x17, 0x43, 0x68, 0x61, 0x6e, + 0x6e, 0x65, 0x6c, 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, + 0x70, 0x6c, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x75, 0x62, 0x6b, 0x65, + 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x12, + 0x12, 0x0a, 0x04, 0x68, 0x6f, 0x73, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, + 0x6f, 0x73, 0x74, 0x12, 0x2a, 0x0a, 0x10, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x63, + 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x63, + 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x63, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x12, + 0x20, 0x0a, 0x0b, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x63, 0x6f, 0x6e, + 0x66, 0x12, 0x24, 0x0a, 0x0d, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x66, 0x65, 0x65, 0x5f, 0x6d, 0x73, + 0x61, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x66, + 0x65, 0x65, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x66, 0x65, 0x65, 0x5f, 0x72, + 0x61, 0x74, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x01, 0x52, 0x08, 0x66, 0x65, 0x65, 0x5f, 0x72, + 0x61, 0x74, 0x65, 0x12, 0x28, 0x0a, 0x0f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6c, 0x6f, 0x63, 0x6b, + 0x5f, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0f, 0x74, 0x69, + 0x6d, 0x65, 0x5f, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x12, 0x24, 0x0a, + 0x0d, 0x6d, 0x69, 0x6e, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x09, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x6d, 0x69, 0x6e, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x6d, + 0x73, 0x61, 0x74, 0x12, 0x32, 0x0a, 0x15, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x66, + 0x65, 0x65, 0x5f, 0x70, 0x65, 0x72, 0x6d, 0x79, 0x72, 0x69, 0x61, 0x64, 0x18, 0x0a, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x13, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x46, 0x65, 0x65, 0x50, 0x65, + 0x72, 0x6d, 0x79, 0x72, 0x69, 0x61, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x6c, 0x73, 0x70, 0x5f, 0x70, + 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x6c, 0x73, 0x70, + 0x50, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x12, 0x32, 0x0a, 0x15, 0x6d, 0x61, 0x78, 0x5f, 0x69, 0x6e, + 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, + 0x0c, 0x20, 0x01, 0x28, 0x03, 0x52, 0x13, 0x6d, 0x61, 0x78, 0x49, 0x6e, 0x61, 0x63, 0x74, 0x69, + 0x76, 0x65, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x37, 0x0a, 0x18, 0x63, 0x68, + 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x6d, 0x69, 0x6e, 0x69, 0x6d, 0x75, 0x6d, 0x5f, 0x66, 0x65, + 0x65, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x03, 0x52, 0x15, 0x63, 0x68, + 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x4d, 0x69, 0x6e, 0x69, 0x6d, 0x75, 0x6d, 0x46, 0x65, 0x65, 0x4d, + 0x73, 0x61, 0x74, 0x22, 0x2c, 0x0a, 0x12, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, + 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x75, 0x62, + 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x75, 0x62, 0x6b, 0x65, + 0x79, 0x22, 0x50, 0x0a, 0x10, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, + 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x74, 0x78, 0x5f, 0x68, 0x61, 0x73, 0x68, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x74, 0x78, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x12, + 0x22, 0x0a, 0x0c, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0c, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x5f, 0x69, 0x6e, + 0x64, 0x65, 0x78, 0x22, 0x2c, 0x0a, 0x16, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x50, + 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, + 0x04, 0x62, 0x6c, 0x6f, 0x62, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x62, 0x6c, 0x6f, + 0x62, 0x22, 0x16, 0x0a, 0x14, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x50, 0x61, 0x79, + 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0xf6, 0x01, 0x0a, 0x12, 0x50, 0x61, + 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x68, 0x61, 0x73, 0x68, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x48, + 0x61, 0x73, 0x68, 0x12, 0x25, 0x0a, 0x0e, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x73, + 0x65, 0x63, 0x72, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x70, 0x61, 0x79, + 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, + 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, + 0x0b, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x30, 0x0a, 0x14, + 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, + 0x6d, 0x73, 0x61, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x12, 0x69, 0x6e, 0x63, 0x6f, + 0x6d, 0x69, 0x6e, 0x67, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x30, + 0x0a, 0x14, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, + 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x12, 0x6f, 0x75, + 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x4d, 0x73, 0x61, 0x74, + 0x12, 0x10, 0x0a, 0x03, 0x74, 0x61, 0x67, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x74, + 0x61, 0x67, 0x22, 0x1f, 0x0a, 0x09, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x12, + 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, + 0x61, 0x74, 0x61, 0x22, 0x52, 0x0a, 0x06, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x12, 0x12, 0x0a, + 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61, 0x74, + 0x61, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x06, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x69, 0x67, + 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x69, + 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0x86, 0x03, 0x0a, 0x14, 0x43, 0x68, 0x65, 0x63, + 0x6b, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x25, 0x0a, 0x0e, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x5f, 0x70, 0x75, 0x62, 0x6b, + 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, + 0x74, 0x50, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x12, 0x51, 0x0a, 0x0d, 0x66, 0x61, 0x6b, 0x65, 0x5f, + 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2c, + 0x2e, 0x6c, 0x73, 0x70, 0x64, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x43, 0x68, 0x61, 0x6e, 0x6e, + 0x65, 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x46, 0x61, 0x6b, 0x65, 0x43, + 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0c, 0x66, 0x61, + 0x6b, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x12, 0x6a, 0x0a, 0x16, 0x77, 0x61, + 0x69, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x5f, 0x63, 0x68, 0x61, 0x6e, + 0x6e, 0x65, 0x6c, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x34, 0x2e, 0x6c, 0x73, 0x70, + 0x64, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x57, 0x61, 0x69, 0x74, 0x69, 0x6e, 0x67, 0x43, 0x6c, + 0x6f, 0x73, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, + 0x52, 0x14, 0x77, 0x61, 0x69, 0x74, 0x69, 0x6e, 0x67, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x43, 0x68, + 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x1a, 0x3f, 0x0a, 0x11, 0x46, 0x61, 0x6b, 0x65, 0x43, 0x68, + 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, + 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, + 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x47, 0x0a, 0x19, 0x57, 0x61, 0x69, 0x74, 0x69, + 0x6e, 0x67, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x45, + 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, + 0x22, 0xcd, 0x02, 0x0a, 0x12, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, + 0x6c, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x59, 0x0a, 0x11, 0x6e, 0x6f, 0x74, 0x5f, 0x66, + 0x61, 0x6b, 0x65, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x18, 0x01, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x6c, 0x73, 0x70, 0x64, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x43, + 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x2e, 0x4e, 0x6f, 0x74, + 0x46, 0x61, 0x6b, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, + 0x79, 0x52, 0x0f, 0x6e, 0x6f, 0x74, 0x46, 0x61, 0x6b, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, + 0x6c, 0x73, 0x12, 0x55, 0x0a, 0x0f, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x5f, 0x63, 0x68, 0x61, + 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x6c, 0x73, + 0x70, 0x64, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, + 0x52, 0x65, 0x70, 0x6c, 0x79, 0x2e, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x43, 0x68, 0x61, 0x6e, + 0x6e, 0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0e, 0x63, 0x6c, 0x6f, 0x73, 0x65, + 0x64, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x1a, 0x42, 0x0a, 0x14, 0x4e, 0x6f, 0x74, + 0x46, 0x61, 0x6b, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, + 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, + 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x04, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x41, 0x0a, + 0x13, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x45, + 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, + 0x32, 0xae, 0x02, 0x0a, 0x0d, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x4f, 0x70, 0x65, 0x6e, + 0x65, 0x72, 0x12, 0x56, 0x0a, 0x12, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x49, 0x6e, 0x66, + 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1f, 0x2e, 0x6c, 0x73, 0x70, 0x64, 0x2e, + 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x6c, 0x73, 0x70, 0x64, + 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x41, 0x0a, 0x0b, 0x4f, 0x70, + 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x18, 0x2e, 0x6c, 0x73, 0x70, 0x64, + 0x2e, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x6c, 0x73, 0x70, 0x64, 0x2e, 0x4f, 0x70, 0x65, 0x6e, 0x43, + 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x4d, 0x0a, + 0x0f, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, + 0x12, 0x1c, 0x2e, 0x6c, 0x73, 0x70, 0x64, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, + 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, + 0x2e, 0x6c, 0x73, 0x70, 0x64, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x50, 0x61, + 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x33, 0x0a, 0x0d, + 0x43, 0x68, 0x65, 0x63, 0x6b, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x12, 0x0f, 0x2e, + 0x6c, 0x73, 0x70, 0x64, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x1a, 0x0f, + 0x2e, 0x6c, 0x73, 0x70, 0x64, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x22, + 0x00, 0x42, 0x3a, 0x0a, 0x14, 0x69, 0x6f, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x6c, 0x73, 0x70, + 0x64, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x42, 0x09, 0x4c, 0x73, 0x70, 0x64, 0x50, + 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x15, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, + 0x6f, 0x6d, 0x2f, 0x62, 0x72, 0x65, 0x65, 0x7a, 0x2f, 0x6c, 0x73, 0x70, 0x64, 0x62, 0x06, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x33, } -func init() { - proto.RegisterFile("lspd.proto", fileDescriptor_c69a0f5a734bca26) +var ( + file_lspd_proto_rawDescOnce sync.Once + file_lspd_proto_rawDescData = file_lspd_proto_rawDesc +) + +func file_lspd_proto_rawDescGZIP() []byte { + file_lspd_proto_rawDescOnce.Do(func() { + file_lspd_proto_rawDescData = protoimpl.X.CompressGZIP(file_lspd_proto_rawDescData) + }) + return file_lspd_proto_rawDescData } -var fileDescriptor_c69a0f5a734bca26 = []byte{ - // 899 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x56, 0xef, 0x6e, 0xe3, 0x44, - 0x10, 0x3f, 0x37, 0x69, 0xef, 0x32, 0x71, 0x9a, 0xde, 0x5e, 0x2e, 0xf8, 0xa2, 0x3b, 0x5d, 0x30, - 0x9c, 0x14, 0xa1, 0x12, 0xa1, 0x16, 0x09, 0xc4, 0x17, 0xd4, 0x96, 0x2b, 0x9c, 0x44, 0x21, 0xf8, - 0x04, 0x88, 0x4f, 0xd6, 0xc6, 0x9e, 0x24, 0x4b, 0xec, 0xb5, 0xb1, 0xd7, 0xbd, 0xe6, 0x05, 0x78, - 0x1c, 0xde, 0x84, 0xb7, 0xe0, 0x25, 0xf8, 0x86, 0xf6, 0x8f, 0x1b, 0x27, 0x71, 0x81, 0x7e, 0x5b, - 0xff, 0x66, 0xe6, 0x37, 0xb3, 0xbf, 0x99, 0x9d, 0x04, 0x20, 0xca, 0xd3, 0x70, 0x9c, 0x66, 0x89, - 0x48, 0x48, 0x53, 0x9e, 0xdd, 0x53, 0x78, 0x76, 0xb1, 0xa0, 0x9c, 0x63, 0xf4, 0x86, 0xcf, 0x92, - 0x2c, 0xa6, 0x82, 0x25, 0xdc, 0xc3, 0xdf, 0x0a, 0xcc, 0x05, 0xe9, 0xc3, 0x41, 0x5a, 0x4c, 0x97, - 0xb8, 0x72, 0xac, 0xa1, 0x35, 0x6a, 0x79, 0xe6, 0xcb, 0xfd, 0xbb, 0x01, 0xef, 0xd5, 0x45, 0xa5, - 0xd1, 0x8a, 0x10, 0x68, 0x72, 0x1a, 0xa3, 0x89, 0x50, 0xe7, 0x0a, 0xcf, 0x5e, 0x95, 0x47, 0xfa, - 0x2e, 0x92, 0x5c, 0x38, 0x0d, 0xed, 0x2b, 0xcf, 0xe4, 0x23, 0x38, 0x0a, 0x34, 0xb5, 0x1f, 0xd0, - 0x94, 0x06, 0x4c, 0xac, 0x9c, 0xe6, 0xd0, 0x1a, 0x35, 0xbc, 0x1d, 0x9c, 0x0c, 0xa1, 0x2d, 0x68, - 0x36, 0x47, 0xe1, 0x07, 0x09, 0x9f, 0x39, 0xfb, 0x43, 0x6b, 0xb4, 0xef, 0x55, 0x21, 0xf2, 0x21, - 0x74, 0xa6, 0x34, 0x47, 0x7f, 0x86, 0xe8, 0xc7, 0x39, 0x15, 0xce, 0x81, 0xa2, 0xda, 0x04, 0xc9, - 0x00, 0x1e, 0xc9, 0x73, 0x46, 0x05, 0x3a, 0x0f, 0x87, 0xd6, 0xc8, 0xf2, 0x6e, 0xbf, 0xc9, 0x08, - 0xba, 0x82, 0xc5, 0xe8, 0x47, 0x49, 0xb0, 0xf4, 0x43, 0x8c, 0x04, 0x75, 0x1e, 0x0d, 0xad, 0x51, - 0xc7, 0xdb, 0x86, 0x65, 0xae, 0x98, 0x71, 0x7f, 0x21, 0xa2, 0x40, 0xe7, 0x6a, 0xe9, 0x5c, 0x1b, - 0x20, 0x39, 0x81, 0xa7, 0xe5, 0x3d, 0x64, 0x8e, 0x14, 0xb3, 0x78, 0x95, 0x31, 0x1a, 0x3a, 0xa0, - 0xbc, 0x9f, 0x18, 0xe3, 0x25, 0xe2, 0xa4, 0x34, 0x91, 0x17, 0xaa, 0x71, 0xbe, 0xd1, 0xb0, 0x3d, - 0xb4, 0x46, 0xb6, 0xd7, 0x8a, 0xf2, 0x74, 0xa2, 0x65, 0x3c, 0x81, 0xa7, 0x31, 0xbd, 0xf1, 0x19, - 0xa7, 0x81, 0x60, 0xd7, 0xe8, 0x87, 0x45, 0xa6, 0x1a, 0xe2, 0xd8, 0x9a, 0x32, 0xa6, 0x37, 0x6f, - 0x8c, 0xed, 0x2b, 0x63, 0x22, 0x9f, 0x81, 0x53, 0x96, 0x11, 0x33, 0xce, 0xe2, 0x22, 0x5e, 0x6b, - 0xd4, 0x51, 0x61, 0x65, 0x99, 0x57, 0xda, 0x7c, 0x89, 0x78, 0x95, 0x53, 0xe1, 0x1e, 0x03, 0xf9, - 0x3e, 0x45, 0x6e, 0xda, 0xff, 0x5f, 0x93, 0x32, 0x81, 0xa3, 0x0d, 0x6f, 0x39, 0x21, 0x0e, 0x3c, - 0x14, 0x37, 0xfe, 0x82, 0xe6, 0x0b, 0xe3, 0x5c, 0x7e, 0x12, 0x17, 0xec, 0xa4, 0x10, 0x69, 0x21, - 0x7c, 0xc6, 0x43, 0xbc, 0x51, 0xd3, 0xd2, 0xf1, 0x36, 0x30, 0xf7, 0x18, 0xfa, 0x1e, 0xce, 0x59, - 0x2e, 0x30, 0x9b, 0xd0, 0x55, 0x8c, 0x5c, 0x94, 0x35, 0x10, 0x68, 0x4e, 0xa3, 0x64, 0xaa, 0xa6, - 0xc9, 0xf6, 0xd4, 0xd9, 0xed, 0x43, 0x6f, 0xc7, 0x3b, 0x8d, 0x56, 0xee, 0x5f, 0x16, 0x10, 0x03, - 0x54, 0x26, 0x98, 0xbc, 0x0f, 0x76, 0xaa, 0xd1, 0x75, 0x7d, 0xb6, 0xd7, 0x36, 0xd8, 0x37, 0xb2, - 0xc6, 0x57, 0x70, 0x58, 0xba, 0xe4, 0x18, 0x64, 0x28, 0x54, 0x95, 0xb6, 0xd7, 0x31, 0xe8, 0x5b, - 0x05, 0xca, 0xd1, 0x0c, 0x31, 0x17, 0x8c, 0xeb, 0x4e, 0xe8, 0x9a, 0xaa, 0x10, 0xf9, 0x04, 0x7a, - 0x8c, 0x07, 0x49, 0xcc, 0xf8, 0xdc, 0xa7, 0x71, 0x52, 0x70, 0xa1, 0xd5, 0xd7, 0xc3, 0x4e, 0x4a, - 0xdb, 0x99, 0x32, 0x49, 0xe9, 0x65, 0x44, 0x52, 0x88, 0x79, 0xb2, 0x1d, 0xb1, 0xaf, 0x23, 0x4a, - 0xdb, 0x3a, 0xc2, 0x7d, 0x09, 0xad, 0xd7, 0x3c, 0xc8, 0x56, 0xa9, 0xc0, 0x50, 0xea, 0x13, 0x52, - 0x41, 0xcd, 0xa5, 0xd4, 0xd9, 0xf5, 0xe0, 0xe0, 0x2d, 0x9b, 0xf3, 0x7a, 0xeb, 0xd6, 0xbb, 0xb5, - 0x6f, 0xdf, 0xed, 0x73, 0x68, 0xe5, 0x6c, 0xce, 0xa9, 0x28, 0x32, 0x34, 0x57, 0x5b, 0x03, 0xee, - 0xef, 0x0d, 0xe8, 0x5d, 0x2c, 0x30, 0x58, 0x9a, 0xae, 0xe7, 0x65, 0x83, 0x5e, 0xc1, 0x21, 0xea, - 0x6a, 0xfc, 0xca, 0xb0, 0xd8, 0x5e, 0xc7, 0xa0, 0x66, 0x9c, 0x7f, 0x80, 0xce, 0x8c, 0x2e, 0xd1, - 0x37, 0xf3, 0x97, 0x3b, 0x7b, 0xc3, 0xc6, 0xa8, 0x7d, 0x72, 0x3c, 0x56, 0xcb, 0xab, 0x8e, 0x79, - 0x7c, 0x49, 0x97, 0x58, 0x62, 0xaf, 0xb9, 0xc8, 0x56, 0x9e, 0x3d, 0xab, 0x40, 0xe4, 0x57, 0xe8, - 0xbf, 0xa3, 0x4c, 0x48, 0xe1, 0x82, 0x28, 0xc9, 0x2b, 0xdc, 0x0d, 0xc5, 0xfd, 0xe9, 0xbf, 0x70, - 0xff, 0xac, 0x03, 0x2f, 0x64, 0xdc, 0x66, 0x8e, 0xde, 0xbb, 0x1a, 0xd3, 0xe0, 0x4b, 0x78, 0xbc, - 0x53, 0x0e, 0x39, 0x82, 0xc6, 0xfa, 0x71, 0xc8, 0x23, 0xe9, 0xc1, 0xfe, 0x35, 0x8d, 0x0a, 0x54, - 0xd2, 0x36, 0x3d, 0xfd, 0xf1, 0xc5, 0xde, 0xe7, 0xd6, 0xe0, 0x6b, 0x78, 0x76, 0x67, 0xce, 0xfb, - 0x10, 0xb9, 0x7f, 0xee, 0x01, 0xd9, 0xba, 0x92, 0x7c, 0x7f, 0xbf, 0xc0, 0x63, 0x9e, 0x08, 0x7f, - 0x53, 0x63, 0x4b, 0xe9, 0xf0, 0x71, 0xad, 0x0e, 0x69, 0xb4, 0x1a, 0x7f, 0x97, 0x88, 0x5d, 0x91, - 0xbb, 0x7c, 0x13, 0x25, 0x3f, 0x42, 0x57, 0xe9, 0x1b, 0xfe, 0xbf, 0xe6, 0x49, 0x62, 0x75, 0xc7, - 0x70, 0x93, 0xf7, 0x30, 0xd8, 0x00, 0x07, 0xe7, 0xd0, 0xab, 0xcb, 0x7f, 0x2f, 0x55, 0xcf, 0xe0, - 0x49, 0x4d, 0xaa, 0xfb, 0x50, 0x9c, 0xfc, 0xb1, 0x07, 0x1d, 0x13, 0x2d, 0x97, 0x1a, 0x66, 0xe4, - 0x27, 0x29, 0xf0, 0xf6, 0xef, 0x20, 0x79, 0x59, 0x5e, 0xf6, 0x8e, 0xdf, 0xd5, 0xc1, 0x8b, 0xbb, - 0x1d, 0xe4, 0x72, 0x7a, 0x40, 0xce, 0xa0, 0x5d, 0x59, 0x9b, 0xc4, 0xd1, 0xfe, 0xbb, 0x7b, 0x77, - 0xd0, 0xaf, 0xb1, 0x68, 0x8a, 0x2b, 0xe8, 0x6e, 0x6d, 0x3e, 0xf2, 0x5c, 0x3b, 0xd7, 0xaf, 0xcf, - 0xc1, 0xe0, 0x0e, 0xab, 0xa6, 0x3b, 0x95, 0x57, 0xaf, 0x34, 0x8f, 0x74, 0xb5, 0xfb, 0xed, 0x7a, - 0x19, 0x6c, 0x03, 0xee, 0x83, 0xf3, 0x0f, 0xa0, 0xc7, 0x92, 0xf1, 0x3c, 0x4b, 0x03, 0x6d, 0xcb, - 0x31, 0xbb, 0x66, 0x01, 0x9e, 0xb7, 0xbe, 0xcd, 0xd3, 0x70, 0x22, 0xff, 0x85, 0x4c, 0xac, 0xe9, - 0x81, 0xfa, 0x3b, 0x72, 0xfa, 0x4f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x6e, 0x2c, 0x67, 0xf7, 0x9c, - 0x08, 0x00, 0x00, +var file_lspd_proto_msgTypes = make([]protoimpl.MessageInfo, 15) +var file_lspd_proto_goTypes = []interface{}{ + (*ChannelInformationRequest)(nil), // 0: lspd.ChannelInformationRequest + (*ChannelInformationReply)(nil), // 1: lspd.ChannelInformationReply + (*OpenChannelRequest)(nil), // 2: lspd.OpenChannelRequest + (*OpenChannelReply)(nil), // 3: lspd.OpenChannelReply + (*RegisterPaymentRequest)(nil), // 4: lspd.RegisterPaymentRequest + (*RegisterPaymentReply)(nil), // 5: lspd.RegisterPaymentReply + (*PaymentInformation)(nil), // 6: lspd.PaymentInformation + (*Encrypted)(nil), // 7: lspd.Encrypted + (*Signed)(nil), // 8: lspd.Signed + (*CheckChannelsRequest)(nil), // 9: lspd.CheckChannelsRequest + (*CheckChannelsReply)(nil), // 10: lspd.CheckChannelsReply + nil, // 11: lspd.CheckChannelsRequest.FakeChannelsEntry + nil, // 12: lspd.CheckChannelsRequest.WaitingCloseChannelsEntry + nil, // 13: lspd.CheckChannelsReply.NotFakeChannelsEntry + nil, // 14: lspd.CheckChannelsReply.ClosedChannelsEntry +} +var file_lspd_proto_depIdxs = []int32{ + 11, // 0: lspd.CheckChannelsRequest.fake_channels:type_name -> lspd.CheckChannelsRequest.FakeChannelsEntry + 12, // 1: lspd.CheckChannelsRequest.waiting_close_channels:type_name -> lspd.CheckChannelsRequest.WaitingCloseChannelsEntry + 13, // 2: lspd.CheckChannelsReply.not_fake_channels:type_name -> lspd.CheckChannelsReply.NotFakeChannelsEntry + 14, // 3: lspd.CheckChannelsReply.closed_channels:type_name -> lspd.CheckChannelsReply.ClosedChannelsEntry + 0, // 4: lspd.ChannelOpener.ChannelInformation:input_type -> lspd.ChannelInformationRequest + 2, // 5: lspd.ChannelOpener.OpenChannel:input_type -> lspd.OpenChannelRequest + 4, // 6: lspd.ChannelOpener.RegisterPayment:input_type -> lspd.RegisterPaymentRequest + 7, // 7: lspd.ChannelOpener.CheckChannels:input_type -> lspd.Encrypted + 1, // 8: lspd.ChannelOpener.ChannelInformation:output_type -> lspd.ChannelInformationReply + 3, // 9: lspd.ChannelOpener.OpenChannel:output_type -> lspd.OpenChannelReply + 5, // 10: lspd.ChannelOpener.RegisterPayment:output_type -> lspd.RegisterPaymentReply + 7, // 11: lspd.ChannelOpener.CheckChannels:output_type -> lspd.Encrypted + 8, // [8:12] is the sub-list for method output_type + 4, // [4:8] is the sub-list for method input_type + 4, // [4:4] is the sub-list for extension type_name + 4, // [4:4] is the sub-list for extension extendee + 0, // [0:4] is the sub-list for field type_name } -// Reference imports to suppress errors if they are not otherwise used. -var _ context.Context -var _ grpc.ClientConnInterface - -// This is a compile-time assertion to ensure that this generated file -// is compatible with the grpc package it is being compiled against. -const _ = grpc.SupportPackageIsVersion6 - -// ChannelOpenerClient is the client API for ChannelOpener service. -// -// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. -type ChannelOpenerClient interface { - ChannelInformation(ctx context.Context, in *ChannelInformationRequest, opts ...grpc.CallOption) (*ChannelInformationReply, error) - OpenChannel(ctx context.Context, in *OpenChannelRequest, opts ...grpc.CallOption) (*OpenChannelReply, error) - RegisterPayment(ctx context.Context, in *RegisterPaymentRequest, opts ...grpc.CallOption) (*RegisterPaymentReply, error) - CheckChannels(ctx context.Context, in *Encrypted, opts ...grpc.CallOption) (*Encrypted, error) -} - -type channelOpenerClient struct { - cc grpc.ClientConnInterface -} - -func NewChannelOpenerClient(cc grpc.ClientConnInterface) ChannelOpenerClient { - return &channelOpenerClient{cc} -} - -func (c *channelOpenerClient) ChannelInformation(ctx context.Context, in *ChannelInformationRequest, opts ...grpc.CallOption) (*ChannelInformationReply, error) { - out := new(ChannelInformationReply) - err := c.cc.Invoke(ctx, "/lspd.ChannelOpener/ChannelInformation", in, out, opts...) - if err != nil { - return nil, err +func init() { file_lspd_proto_init() } +func file_lspd_proto_init() { + if File_lspd_proto != nil { + return } - return out, nil -} - -func (c *channelOpenerClient) OpenChannel(ctx context.Context, in *OpenChannelRequest, opts ...grpc.CallOption) (*OpenChannelReply, error) { - out := new(OpenChannelReply) - err := c.cc.Invoke(ctx, "/lspd.ChannelOpener/OpenChannel", in, out, opts...) - if err != nil { - return nil, err + if !protoimpl.UnsafeEnabled { + file_lspd_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ChannelInformationRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lspd_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ChannelInformationReply); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lspd_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*OpenChannelRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lspd_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*OpenChannelReply); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lspd_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RegisterPaymentRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lspd_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RegisterPaymentReply); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lspd_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*PaymentInformation); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lspd_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Encrypted); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lspd_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Signed); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lspd_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CheckChannelsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lspd_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CheckChannelsReply); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } } - return out, nil -} - -func (c *channelOpenerClient) RegisterPayment(ctx context.Context, in *RegisterPaymentRequest, opts ...grpc.CallOption) (*RegisterPaymentReply, error) { - out := new(RegisterPaymentReply) - err := c.cc.Invoke(ctx, "/lspd.ChannelOpener/RegisterPayment", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *channelOpenerClient) CheckChannels(ctx context.Context, in *Encrypted, opts ...grpc.CallOption) (*Encrypted, error) { - out := new(Encrypted) - err := c.cc.Invoke(ctx, "/lspd.ChannelOpener/CheckChannels", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -// ChannelOpenerServer is the server API for ChannelOpener service. -type ChannelOpenerServer interface { - ChannelInformation(context.Context, *ChannelInformationRequest) (*ChannelInformationReply, error) - OpenChannel(context.Context, *OpenChannelRequest) (*OpenChannelReply, error) - RegisterPayment(context.Context, *RegisterPaymentRequest) (*RegisterPaymentReply, error) - CheckChannels(context.Context, *Encrypted) (*Encrypted, error) -} - -// UnimplementedChannelOpenerServer can be embedded to have forward compatible implementations. -type UnimplementedChannelOpenerServer struct { -} - -func (*UnimplementedChannelOpenerServer) ChannelInformation(ctx context.Context, req *ChannelInformationRequest) (*ChannelInformationReply, error) { - return nil, status.Errorf(codes.Unimplemented, "method ChannelInformation not implemented") -} -func (*UnimplementedChannelOpenerServer) OpenChannel(ctx context.Context, req *OpenChannelRequest) (*OpenChannelReply, error) { - return nil, status.Errorf(codes.Unimplemented, "method OpenChannel not implemented") -} -func (*UnimplementedChannelOpenerServer) RegisterPayment(ctx context.Context, req *RegisterPaymentRequest) (*RegisterPaymentReply, error) { - return nil, status.Errorf(codes.Unimplemented, "method RegisterPayment not implemented") -} -func (*UnimplementedChannelOpenerServer) CheckChannels(ctx context.Context, req *Encrypted) (*Encrypted, error) { - return nil, status.Errorf(codes.Unimplemented, "method CheckChannels not implemented") -} - -func RegisterChannelOpenerServer(s *grpc.Server, srv ChannelOpenerServer) { - s.RegisterService(&_ChannelOpener_serviceDesc, srv) -} - -func _ChannelOpener_ChannelInformation_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(ChannelInformationRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(ChannelOpenerServer).ChannelInformation(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/lspd.ChannelOpener/ChannelInformation", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(ChannelOpenerServer).ChannelInformation(ctx, req.(*ChannelInformationRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _ChannelOpener_OpenChannel_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(OpenChannelRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(ChannelOpenerServer).OpenChannel(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/lspd.ChannelOpener/OpenChannel", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(ChannelOpenerServer).OpenChannel(ctx, req.(*OpenChannelRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _ChannelOpener_RegisterPayment_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(RegisterPaymentRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(ChannelOpenerServer).RegisterPayment(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/lspd.ChannelOpener/RegisterPayment", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(ChannelOpenerServer).RegisterPayment(ctx, req.(*RegisterPaymentRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _ChannelOpener_CheckChannels_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(Encrypted) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(ChannelOpenerServer).CheckChannels(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/lspd.ChannelOpener/CheckChannels", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(ChannelOpenerServer).CheckChannels(ctx, req.(*Encrypted)) - } - return interceptor(ctx, in, info, handler) -} - -var _ChannelOpener_serviceDesc = grpc.ServiceDesc{ - ServiceName: "lspd.ChannelOpener", - HandlerType: (*ChannelOpenerServer)(nil), - Methods: []grpc.MethodDesc{ - { - MethodName: "ChannelInformation", - Handler: _ChannelOpener_ChannelInformation_Handler, + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_lspd_proto_rawDesc, + NumEnums: 0, + NumMessages: 15, + NumExtensions: 0, + NumServices: 1, }, - { - MethodName: "OpenChannel", - Handler: _ChannelOpener_OpenChannel_Handler, - }, - { - MethodName: "RegisterPayment", - Handler: _ChannelOpener_RegisterPayment_Handler, - }, - { - MethodName: "CheckChannels", - Handler: _ChannelOpener_CheckChannels_Handler, - }, - }, - Streams: []grpc.StreamDesc{}, - Metadata: "lspd.proto", + GoTypes: file_lspd_proto_goTypes, + DependencyIndexes: file_lspd_proto_depIdxs, + MessageInfos: file_lspd_proto_msgTypes, + }.Build() + File_lspd_proto = out.File + file_lspd_proto_rawDesc = nil + file_lspd_proto_goTypes = nil + file_lspd_proto_depIdxs = nil } diff --git a/rpc/lspd.proto b/rpc/lspd.proto index 450871d..45cbc30 100644 --- a/rpc/lspd.proto +++ b/rpc/lspd.proto @@ -3,6 +3,7 @@ syntax = "proto3"; option java_multiple_files = true; option java_package = "io.grpc.lspd.service"; option java_outer_classname = "LspdProto"; +option go_package = "github.com/breez/lspd"; package lspd; @@ -78,6 +79,7 @@ message PaymentInformation { bytes destination = 3; int64 incoming_amount_msat = 4; int64 outgoing_amount_msat = 5; + string tag = 6; } message Encrypted { diff --git a/rpc/lspd_grpc.pb.go b/rpc/lspd_grpc.pb.go new file mode 100644 index 0000000..d2160bc --- /dev/null +++ b/rpc/lspd_grpc.pb.go @@ -0,0 +1,213 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.2.0 +// - protoc v3.21.12 +// source: lspd.proto + +package lspd + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +// ChannelOpenerClient is the client API for ChannelOpener service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type ChannelOpenerClient interface { + ChannelInformation(ctx context.Context, in *ChannelInformationRequest, opts ...grpc.CallOption) (*ChannelInformationReply, error) + OpenChannel(ctx context.Context, in *OpenChannelRequest, opts ...grpc.CallOption) (*OpenChannelReply, error) + RegisterPayment(ctx context.Context, in *RegisterPaymentRequest, opts ...grpc.CallOption) (*RegisterPaymentReply, error) + CheckChannels(ctx context.Context, in *Encrypted, opts ...grpc.CallOption) (*Encrypted, error) +} + +type channelOpenerClient struct { + cc grpc.ClientConnInterface +} + +func NewChannelOpenerClient(cc grpc.ClientConnInterface) ChannelOpenerClient { + return &channelOpenerClient{cc} +} + +func (c *channelOpenerClient) ChannelInformation(ctx context.Context, in *ChannelInformationRequest, opts ...grpc.CallOption) (*ChannelInformationReply, error) { + out := new(ChannelInformationReply) + err := c.cc.Invoke(ctx, "/lspd.ChannelOpener/ChannelInformation", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *channelOpenerClient) OpenChannel(ctx context.Context, in *OpenChannelRequest, opts ...grpc.CallOption) (*OpenChannelReply, error) { + out := new(OpenChannelReply) + err := c.cc.Invoke(ctx, "/lspd.ChannelOpener/OpenChannel", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *channelOpenerClient) RegisterPayment(ctx context.Context, in *RegisterPaymentRequest, opts ...grpc.CallOption) (*RegisterPaymentReply, error) { + out := new(RegisterPaymentReply) + err := c.cc.Invoke(ctx, "/lspd.ChannelOpener/RegisterPayment", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *channelOpenerClient) CheckChannels(ctx context.Context, in *Encrypted, opts ...grpc.CallOption) (*Encrypted, error) { + out := new(Encrypted) + err := c.cc.Invoke(ctx, "/lspd.ChannelOpener/CheckChannels", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// ChannelOpenerServer is the server API for ChannelOpener service. +// All implementations must embed UnimplementedChannelOpenerServer +// for forward compatibility +type ChannelOpenerServer interface { + ChannelInformation(context.Context, *ChannelInformationRequest) (*ChannelInformationReply, error) + OpenChannel(context.Context, *OpenChannelRequest) (*OpenChannelReply, error) + RegisterPayment(context.Context, *RegisterPaymentRequest) (*RegisterPaymentReply, error) + CheckChannels(context.Context, *Encrypted) (*Encrypted, error) + mustEmbedUnimplementedChannelOpenerServer() +} + +// UnimplementedChannelOpenerServer must be embedded to have forward compatible implementations. +type UnimplementedChannelOpenerServer struct { +} + +func (UnimplementedChannelOpenerServer) ChannelInformation(context.Context, *ChannelInformationRequest) (*ChannelInformationReply, error) { + return nil, status.Errorf(codes.Unimplemented, "method ChannelInformation not implemented") +} +func (UnimplementedChannelOpenerServer) OpenChannel(context.Context, *OpenChannelRequest) (*OpenChannelReply, error) { + return nil, status.Errorf(codes.Unimplemented, "method OpenChannel not implemented") +} +func (UnimplementedChannelOpenerServer) RegisterPayment(context.Context, *RegisterPaymentRequest) (*RegisterPaymentReply, error) { + return nil, status.Errorf(codes.Unimplemented, "method RegisterPayment not implemented") +} +func (UnimplementedChannelOpenerServer) CheckChannels(context.Context, *Encrypted) (*Encrypted, error) { + return nil, status.Errorf(codes.Unimplemented, "method CheckChannels not implemented") +} +func (UnimplementedChannelOpenerServer) mustEmbedUnimplementedChannelOpenerServer() {} + +// UnsafeChannelOpenerServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to ChannelOpenerServer will +// result in compilation errors. +type UnsafeChannelOpenerServer interface { + mustEmbedUnimplementedChannelOpenerServer() +} + +func RegisterChannelOpenerServer(s grpc.ServiceRegistrar, srv ChannelOpenerServer) { + s.RegisterService(&ChannelOpener_ServiceDesc, srv) +} + +func _ChannelOpener_ChannelInformation_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ChannelInformationRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ChannelOpenerServer).ChannelInformation(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/lspd.ChannelOpener/ChannelInformation", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ChannelOpenerServer).ChannelInformation(ctx, req.(*ChannelInformationRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _ChannelOpener_OpenChannel_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(OpenChannelRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ChannelOpenerServer).OpenChannel(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/lspd.ChannelOpener/OpenChannel", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ChannelOpenerServer).OpenChannel(ctx, req.(*OpenChannelRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _ChannelOpener_RegisterPayment_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(RegisterPaymentRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ChannelOpenerServer).RegisterPayment(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/lspd.ChannelOpener/RegisterPayment", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ChannelOpenerServer).RegisterPayment(ctx, req.(*RegisterPaymentRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _ChannelOpener_CheckChannels_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Encrypted) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ChannelOpenerServer).CheckChannels(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/lspd.ChannelOpener/CheckChannels", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ChannelOpenerServer).CheckChannels(ctx, req.(*Encrypted)) + } + return interceptor(ctx, in, info, handler) +} + +// ChannelOpener_ServiceDesc is the grpc.ServiceDesc for ChannelOpener service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var ChannelOpener_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "lspd.ChannelOpener", + HandlerType: (*ChannelOpenerServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "ChannelInformation", + Handler: _ChannelOpener_ChannelInformation_Handler, + }, + { + MethodName: "OpenChannel", + Handler: _ChannelOpener_OpenChannel_Handler, + }, + { + MethodName: "RegisterPayment", + Handler: _ChannelOpener_RegisterPayment_Handler, + }, + { + MethodName: "CheckChannels", + Handler: _ChannelOpener_CheckChannels_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "lspd.proto", +} diff --git a/server.go b/server.go index e777784..b045d74 100644 --- a/server.go +++ b/server.go @@ -28,6 +28,7 @@ import ( ) type server struct { + lspdrpc.ChannelOpenerServer address string certmagicDomain string lis net.Listener From 993193feac26c297b85bb70984797beed4dc25f5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 15 Feb 2023 01:43:38 +0000 Subject: [PATCH 153/214] Bump github.com/prometheus/client_golang from 1.11.0 to 1.11.1 Bumps [github.com/prometheus/client_golang](https://github.com/prometheus/client_golang) from 1.11.0 to 1.11.1. - [Release notes](https://github.com/prometheus/client_golang/releases) - [Changelog](https://github.com/prometheus/client_golang/blob/main/CHANGELOG.md) - [Commits](https://github.com/prometheus/client_golang/compare/v1.11.0...v1.11.1) --- updated-dependencies: - dependency-name: github.com/prometheus/client_golang dependency-type: indirect ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index e5af55e..b4efa92 100644 --- a/go.mod +++ b/go.mod @@ -121,7 +121,7 @@ require ( github.com/nwaples/rardecode v1.1.2 // indirect github.com/pierrec/lz4/v4 v4.1.8 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/prometheus/client_golang v1.11.0 // indirect + github.com/prometheus/client_golang v1.11.1 // indirect github.com/prometheus/client_model v0.2.0 // indirect github.com/prometheus/common v0.26.0 // indirect github.com/prometheus/procfs v0.6.0 // indirect From 8e2c9bd9ce4fee54f875e394d9a3e62907f23cb9 Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Mon, 13 Feb 2023 10:07:10 +0100 Subject: [PATCH 154/214] set the tag field on registerpayment --- db.go | 20 +++++++++++-------- .../000009_register_payment_tag.down.sql | 1 + .../000009_register_payment_tag.up.sql | 1 + server.go | 20 ++++++++++++++++--- 4 files changed, 31 insertions(+), 11 deletions(-) create mode 100644 postgresql/migrations/000009_register_payment_tag.down.sql create mode 100644 postgresql/migrations/000009_register_payment_tag.up.sql diff --git a/db.go b/db.go index 4e40169..b0332ec 100644 --- a/db.go +++ b/db.go @@ -64,18 +64,22 @@ func setFundingTx(paymentHash []byte, channelPoint *wire.OutPoint) error { return err } -func registerPayment(destination, paymentHash, paymentSecret []byte, incomingAmountMsat, outgoingAmountMsat int64) error { +func registerPayment(destination, paymentHash, paymentSecret []byte, incomingAmountMsat, outgoingAmountMsat int64, tag string) error { + var t *string + if tag != "" { + t = &tag + } commandTag, err := pgxPool.Exec(context.Background(), `INSERT INTO - payments (destination, payment_hash, payment_secret, incoming_amount_msat, outgoing_amount_msat) - VALUES ($1, $2, $3, $4, $5) + payments (destination, payment_hash, payment_secret, incoming_amount_msat, outgoing_amount_msat, tag) + VALUES ($1, $2, $3, $4, $5, $6) ON CONFLICT DO NOTHING`, - destination, paymentHash, paymentSecret, incomingAmountMsat, outgoingAmountMsat) - log.Printf("registerPayment(%x, %x, %x, %v, %v) rows: %v err: %v", - destination, paymentHash, paymentSecret, incomingAmountMsat, outgoingAmountMsat, commandTag.RowsAffected(), err) + destination, paymentHash, paymentSecret, incomingAmountMsat, outgoingAmountMsat, t) + log.Printf("registerPayment(%x, %x, %x, %v, %v, %v) rows: %v err: %v", + destination, paymentHash, paymentSecret, incomingAmountMsat, outgoingAmountMsat, tag, commandTag.RowsAffected(), err) if err != nil { - return fmt.Errorf("registerPayment(%x, %x, %x, %v, %v) error: %w", - destination, paymentHash, paymentSecret, incomingAmountMsat, outgoingAmountMsat, err) + return fmt.Errorf("registerPayment(%x, %x, %x, %v, %v, %v) error: %w", + destination, paymentHash, paymentSecret, incomingAmountMsat, outgoingAmountMsat, tag, err) } return nil } diff --git a/postgresql/migrations/000009_register_payment_tag.down.sql b/postgresql/migrations/000009_register_payment_tag.down.sql new file mode 100644 index 0000000..a41df51 --- /dev/null +++ b/postgresql/migrations/000009_register_payment_tag.down.sql @@ -0,0 +1 @@ +ALTER TABLE public.payments DROP COLUMN tag; diff --git a/postgresql/migrations/000009_register_payment_tag.up.sql b/postgresql/migrations/000009_register_payment_tag.up.sql new file mode 100644 index 0000000..89f004a --- /dev/null +++ b/postgresql/migrations/000009_register_payment_tag.up.sql @@ -0,0 +1 @@ +ALTER TABLE public.payments ADD tag jsonb NULL; diff --git a/server.go b/server.go index b045d74..0bb13c0 100644 --- a/server.go +++ b/server.go @@ -4,6 +4,7 @@ import ( "context" "crypto/tls" "encoding/hex" + "encoding/json" "fmt" "log" "net" @@ -91,14 +92,27 @@ func (s *server) RegisterPayment(ctx context.Context, in *lspdrpc.RegisterPaymen 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) + log.Printf("RegisterPayment - Destination: %x, pi.PaymentHash: %x, pi.PaymentSecret: %x, pi.IncomingAmountMsat: %v, pi.OutgoingAmountMsat: %v, pi.Tag: %v", + pi.Destination, pi.PaymentHash, pi.PaymentSecret, pi.IncomingAmountMsat, pi.OutgoingAmountMsat, pi.Tag) + + if len(pi.Tag) > 1000 { + return nil, fmt.Errorf("tag too long") + } + + if len(pi.Tag) != 0 { + var tag json.RawMessage + err = json.Unmarshal([]byte(pi.Tag), &tag) + if err != nil { + return nil, fmt.Errorf("tag is not a valid json object") + } + } + err = checkPayment(node.nodeConfig, 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) + err = registerPayment(pi.Destination, pi.PaymentHash, pi.PaymentSecret, pi.IncomingAmountMsat, pi.OutgoingAmountMsat, pi.Tag) if err != nil { log.Printf("RegisterPayment() error: %v", err) return nil, fmt.Errorf("RegisterPayment() error: %w", err) From 14f93d934eb14bb58d78c14bd9591e8d9673568e Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Mon, 13 Feb 2023 10:38:51 +0100 Subject: [PATCH 155/214] add a test to verify tag is persisted --- itest/cln_lspd_node.go | 3 +++ itest/lnd_lspd_node.go | 4 +++ itest/lspd_node.go | 1 + itest/lspd_test.go | 4 +++ itest/tag_test.go | 55 ++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 67 insertions(+) create mode 100644 itest/tag_test.go diff --git a/itest/cln_lspd_node.go b/itest/cln_lspd_node.go index 0221e84..52bec20 100644 --- a/itest/cln_lspd_node.go +++ b/itest/cln_lspd_node.go @@ -217,3 +217,6 @@ func (l *ClnLspNode) LightningNode() lntest.LightningNode { func (l *ClnLspNode) SupportsChargingFees() bool { return false } +func (l *ClnLspNode) PostgresBackend() *PostgresContainer { + return l.lspBase.postgresBackend +} diff --git a/itest/lnd_lspd_node.go b/itest/lnd_lspd_node.go index e9c0d10..0b98931 100644 --- a/itest/lnd_lspd_node.go +++ b/itest/lnd_lspd_node.go @@ -240,3 +240,7 @@ func (l *LndLspNode) NodeId() []byte { func (l *LndLspNode) LightningNode() lntest.LightningNode { return l.lightningNode } + +func (l *LndLspNode) PostgresBackend() *PostgresContainer { + return l.lspBase.postgresBackend +} diff --git a/itest/lspd_node.go b/itest/lspd_node.go index abfab8b..2d22fd0 100644 --- a/itest/lspd_node.go +++ b/itest/lspd_node.go @@ -43,6 +43,7 @@ type LspNode interface { NodeId() []byte LightningNode() lntest.LightningNode SupportsChargingFees() bool + PostgresBackend() *PostgresContainer } type lspBase struct { diff --git a/itest/lspd_test.go b/itest/lspd_test.go index b3220f9..f888f82 100644 --- a/itest/lspd_test.go +++ b/itest/lspd_test.go @@ -101,4 +101,8 @@ var allTestCases = []*testCase{ name: "testInvalidCltv", test: testInvalidCltv, }, + { + name: "registerPaymentWithTag", + test: registerPaymentWithTag, + }, } diff --git a/itest/tag_test.go b/itest/tag_test.go new file mode 100644 index 0000000..c671dc4 --- /dev/null +++ b/itest/tag_test.go @@ -0,0 +1,55 @@ +package itest + +import ( + "log" + + "github.com/breez/lntest" + lspd "github.com/breez/lspd/rpc" + "github.com/jackc/pgx/v4/pgxpool" + "github.com/stretchr/testify/assert" +) + +func registerPaymentWithTag(p *testParams) { + expected := "{\"apiKey\": \"11111111111111\"}" + i := p.BreezClient().Node().CreateBolt11Invoice(&lntest.CreateInvoiceOptions{ + AmountMsat: 25000000, + }) + + log.Printf("Registering payment with lsp") + RegisterPayment(p.lsp, &lspd.PaymentInformation{ + PaymentHash: i.PaymentHash, + PaymentSecret: i.PaymentSecret, + Destination: p.BreezClient().Node().NodeId(), + IncomingAmountMsat: int64(25000000), + OutgoingAmountMsat: int64(21000000), + Tag: expected, + }) + + pgxPool, err := pgxpool.Connect(p.h.Ctx, p.lsp.PostgresBackend().ConnectionString()) + if err != nil { + p.h.T.Fatalf("Failed to connect to postgres backend: %v", err) + } + defer pgxPool.Close() + + rows, err := pgxPool.Query( + p.h.Ctx, + "SELECT tag FROM public.payments WHERE payment_hash=$1", + i.PaymentHash, + ) + if err != nil { + p.h.T.Fatalf("Failed to query tag: %v", err) + } + + defer rows.Close() + if !rows.Next() { + p.h.T.Fatal("No rows found") + } + + var actual string + err = rows.Scan(&actual) + if err != nil { + p.h.T.Fatalf("Failed to get tag from row: %v", err) + } + + assert.Equal(p.h.T, expected, actual) +} From 76b6ad98f84eaecc282dba85cea238510dd823b3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 17 Feb 2023 15:33:33 +0000 Subject: [PATCH 156/214] Bump golang.org/x/net from 0.1.0 to 0.7.0 Bumps [golang.org/x/net](https://github.com/golang/net) from 0.1.0 to 0.7.0. - [Release notes](https://github.com/golang/net/releases) - [Commits](https://github.com/golang/net/compare/v0.1.0...v0.7.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-type: indirect ... Signed-off-by: dependabot[bot] --- go.mod | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index e5af55e..1b40096 100644 --- a/go.mod +++ b/go.mod @@ -38,7 +38,7 @@ require ( github.com/opencontainers/image-spec v1.0.2 // indirect github.com/pkg/errors v0.9.1 // indirect gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec // indirect - golang.org/x/net v0.1.0 // indirect + golang.org/x/net v0.7.0 // indirect gotest.tools/v3 v3.4.0 // indirect ) @@ -158,9 +158,9 @@ require ( go.uber.org/multierr v1.8.0 // indirect go.uber.org/zap v1.17.0 // indirect golang.org/x/crypto v0.1.0 // indirect - golang.org/x/sys v0.1.0 // indirect - golang.org/x/term v0.1.0 // indirect - golang.org/x/text v0.4.0 // indirect + golang.org/x/sys v0.5.0 // indirect + golang.org/x/term v0.5.0 // indirect + golang.org/x/text v0.7.0 // indirect golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba // indirect google.golang.org/genproto v0.0.0-20210617175327-b9e0b3197ced // indirect google.golang.org/protobuf v1.27.1 From 3b0dd351f48238fb95a73414e2bd21a727280e81 Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Sat, 4 Mar 2023 12:53:07 +0100 Subject: [PATCH 157/214] Spend unconfirmed utxos if minconfs=0 --- cln_client.go | 8 +++- cln_interceptor.go | 5 ++- config.go => config/config.go | 4 +- go.mod | 2 +- intercept.go | 7 ++-- itest/cln_lspd_node.go | 14 +++---- itest/config_test.go | 2 +- itest/lnd_lspd_node.go | 15 +++---- itest/lspd_node.go | 58 ++++++++++++++++---------- itest/lspd_test.go | 76 ++++++++++++++++++++++++++--------- itest/test_params.go | 16 +++++--- itest/zero_conf_utxo_test.go | 72 +++++++++++++++++++++++++++++++++ lightning_client.go | 2 +- lnd_client.go | 12 +++++- lnd_interceptor.go | 5 ++- main.go | 3 +- server.go | 5 ++- 17 files changed, 227 insertions(+), 79 deletions(-) rename config.go => config/config.go (98%) create mode 100644 itest/zero_conf_utxo_test.go diff --git a/cln_client.go b/cln_client.go index f99ef11..b04e99f 100644 --- a/cln_client.go +++ b/cln_client.go @@ -76,7 +76,11 @@ func (c *ClnClient) IsConnected(destination []byte) (bool, error) { func (c *ClnClient) OpenChannel(req *OpenChannelRequest) (*wire.OutPoint, error) { pubkey := hex.EncodeToString(req.Destination) - minConfs := uint16(req.MinConfs) + var minConfs *uint16 + if req.MinConfs != nil { + m := uint16(*req.MinConfs) + minConfs = &m + } var minDepth *uint16 if req.IsZeroConf { var d uint16 = 0 @@ -110,7 +114,7 @@ func (c *ClnClient) OpenChannel(req *OpenChannelRequest) (*wire.OutPoint, error) glightning.NewSat(int(req.CapacitySat)), rate, !req.IsPrivate, - &minConfs, + minConfs, glightning.NewMsat(0), minDepth, glightning.NewSat(0), diff --git a/cln_interceptor.go b/cln_interceptor.go index ffadb47..bc7b491 100644 --- a/cln_interceptor.go +++ b/cln_interceptor.go @@ -11,6 +11,7 @@ import ( "time" "github.com/breez/lspd/cln_plugin/proto" + "github.com/breez/lspd/config" sphinx "github.com/lightningnetwork/lightning-onion" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/record" @@ -23,7 +24,7 @@ import ( ) type ClnHtlcInterceptor struct { - config *NodeConfig + config *config.NodeConfig pluginAddress string client *ClnClient pluginClient proto.ClnPluginClient @@ -34,7 +35,7 @@ type ClnHtlcInterceptor struct { cancel context.CancelFunc } -func NewClnHtlcInterceptor(conf *NodeConfig) (*ClnHtlcInterceptor, error) { +func NewClnHtlcInterceptor(conf *config.NodeConfig) (*ClnHtlcInterceptor, error) { if conf.Cln == nil { return nil, fmt.Errorf("missing cln config") } diff --git a/config.go b/config/config.go similarity index 98% rename from config.go rename to config/config.go index 1430cf6..4587f85 100644 --- a/config.go +++ b/config/config.go @@ -1,4 +1,4 @@ -package main +package config type NodeConfig struct { // Name of the LSP. If empty, the node's alias will be taken instead. @@ -35,7 +35,7 @@ type NodeConfig struct { // Minimum number of confirmations inputs for zero conf channel opens should // have. - MinConfs uint32 `json:"minConfs,string"` + MinConfs *uint32 `json:"minConfs,string"` // Smallest htlc amount routed over channels opened with the OpenChannel // rpc call. diff --git a/go.mod b/go.mod index e5af55e..83dd399 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.19 require ( github.com/aws/aws-sdk-go v1.30.20 - github.com/breez/lntest v0.0.18 + github.com/breez/lntest v0.0.19 github.com/btcsuite/btcd v0.23.3 github.com/btcsuite/btcd/btcec/v2 v2.2.1 github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 diff --git a/intercept.go b/intercept.go index dea1aa7..2683e21 100644 --- a/intercept.go +++ b/intercept.go @@ -10,6 +10,7 @@ import ( "time" "github.com/breez/lspd/chain" + "github.com/breez/lspd/config" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/wire" sphinx "github.com/lightningnetwork/lightning-onion" @@ -49,7 +50,7 @@ type interceptResult struct { onionBlob []byte } -func intercept(client LightningClient, config *NodeConfig, nextHop string, reqPaymentHash []byte, reqOutgoingAmountMsat uint64, reqOutgoingExpiry uint32, reqIncomingExpiry uint32) interceptResult { +func intercept(client LightningClient, config *config.NodeConfig, nextHop string, reqPaymentHash []byte, reqOutgoingAmountMsat uint64, reqOutgoingExpiry uint32, reqIncomingExpiry uint32) interceptResult { reqPaymentHashStr := hex.EncodeToString(reqPaymentHash) resp, _, _ := payHashGroup.Do(reqPaymentHashStr, func() (interface{}, error) { paymentHash, paymentSecret, destination, incomingAmountMsat, outgoingAmountMsat, channelPoint, err := paymentInfo(reqPaymentHash) @@ -224,7 +225,7 @@ func intercept(client LightningClient, config *NodeConfig, nextHop string, reqPa return resp.(interceptResult) } -func checkPayment(config *NodeConfig, incomingAmountMsat, outgoingAmountMsat int64) error { +func checkPayment(config *config.NodeConfig, incomingAmountMsat, outgoingAmountMsat int64) error { fees := incomingAmountMsat * config.ChannelFeePermyriad / 10_000 / 1_000 * 1_000 if fees < config.ChannelMinimumFeeMsat { fees = config.ChannelMinimumFeeMsat @@ -235,7 +236,7 @@ func checkPayment(config *NodeConfig, incomingAmountMsat, outgoingAmountMsat int return nil } -func openChannel(client LightningClient, config *NodeConfig, paymentHash, destination []byte, incomingAmountMsat int64) (*wire.OutPoint, error) { +func openChannel(client LightningClient, config *config.NodeConfig, paymentHash, destination []byte, incomingAmountMsat int64) (*wire.OutPoint, error) { capacity := incomingAmountMsat/1000 + config.AdditionalChannelCapacity if capacity == config.PublicChannelAmount { capacity++ diff --git a/itest/cln_lspd_node.go b/itest/cln_lspd_node.go index 52bec20..be3d67a 100644 --- a/itest/cln_lspd_node.go +++ b/itest/cln_lspd_node.go @@ -11,6 +11,7 @@ import ( "syscall" "github.com/breez/lntest" + "github.com/breez/lspd/config" lspd "github.com/breez/lspd/rpc" "github.com/btcsuite/btcd/btcec/v2" ecies "github.com/ecies/go/v2" @@ -41,7 +42,7 @@ type clnLspNodeRuntime struct { cleanups []*lntest.Cleanup } -func NewClnLspdNode(h *lntest.TestHarness, m *lntest.Miner, name string) LspNode { +func NewClnLspdNode(h *lntest.TestHarness, m *lntest.Miner, name string, nodeConfig *config.NodeConfig) LspNode { scriptDir := h.GetDirectory("lspd") pluginBinary := *clnPluginExec pluginPort, err := lntest.GetPort() @@ -60,12 +61,11 @@ func NewClnLspdNode(h *lntest.TestHarness, m *lntest.Miner, name string) LspNode "--dev-allowdustreserve=true", } lightningNode := lntest.NewClnNode(h, m, name, args...) - cln := fmt.Sprintf( - `{ "pluginAddress": "%s", "socketPath": "%s" }`, - pluginAddress, - filepath.Join(lightningNode.SocketDir(), lightningNode.SocketFile()), - ) - lspbase, err := newLspd(h, name, nil, &cln) + cln := &config.ClnConfig{ + PluginAddress: pluginAddress, + SocketPath: filepath.Join(lightningNode.SocketDir(), lightningNode.SocketFile()), + } + lspbase, err := newLspd(h, name, nodeConfig, nil, cln) if err != nil { h.T.Fatalf("failed to initialize lspd") } diff --git a/itest/config_test.go b/itest/config_test.go index 8134902..760b89d 100644 --- a/itest/config_test.go +++ b/itest/config_test.go @@ -19,7 +19,7 @@ func TestConfigParameters(t *testing.T) { m := lntest.NewMiner(h) m.Start() - lsp := NewClnLspdNode(h, m, "lsp") + lsp := NewClnLspdNode(h, m, "lsp", nil) lsp.Start() log.Printf("Waiting %v to allow lsp server to activate.", htlcInterceptorDelay) diff --git a/itest/lnd_lspd_node.go b/itest/lnd_lspd_node.go index 0b98931..e8375df 100644 --- a/itest/lnd_lspd_node.go +++ b/itest/lnd_lspd_node.go @@ -13,6 +13,7 @@ import ( "syscall" "github.com/breez/lntest" + "github.com/breez/lspd/config" lspd "github.com/breez/lspd/rpc" "github.com/btcsuite/btcd/btcec/v2" ecies "github.com/ecies/go/v2" @@ -37,7 +38,7 @@ type lndLspNodeRuntime struct { cleanups []*lntest.Cleanup } -func NewLndLspdNode(h *lntest.TestHarness, m *lntest.Miner, name string) LspNode { +func NewLndLspdNode(h *lntest.TestHarness, m *lntest.Miner, name string, nodeConfig *config.NodeConfig) LspNode { args := []string{ "--protocol.zero-conf", "--protocol.option-scid-alias", @@ -50,12 +51,12 @@ func NewLndLspdNode(h *lntest.TestHarness, m *lntest.Miner, name string) LspNode } lightningNode := lntest.NewLndNode(h, m, name, args...) - j, _ := json.Marshal(map[string]string{ - "address": lightningNode.GrpcHost(), - "cert": string(lightningNode.TlsCert()), - "macaroon": hex.EncodeToString(lightningNode.Macaroon())}) - lnd := string(j) - lspBase, err := newLspd(h, name, &lnd, nil) + lnd := &config.LndConfig{ + Address: lightningNode.GrpcHost(), + Cert: string(lightningNode.TlsCert()), + Macaroon: hex.EncodeToString(lightningNode.Macaroon()), + } + lspBase, err := newLspd(h, name, nodeConfig, lnd, nil) if err != nil { h.T.Fatalf("failed to initialize lspd") } diff --git a/itest/lspd_node.go b/itest/lspd_node.go index 2d22fd0..a8d2e70 100644 --- a/itest/lspd_node.go +++ b/itest/lspd_node.go @@ -3,6 +3,8 @@ package itest import ( "bufio" "context" + "encoding/hex" + "encoding/json" "flag" "fmt" "log" @@ -11,6 +13,7 @@ import ( "path/filepath" "github.com/breez/lntest" + "github.com/breez/lspd/config" lspd "github.com/breez/lspd/rpc" "github.com/btcsuite/btcd/btcec/v2" "github.com/decred/dcrd/dcrec/secp256k1/v4" @@ -58,7 +61,7 @@ type lspBase struct { postgresBackend *PostgresContainer } -func newLspd(h *lntest.TestHarness, name string, lnd *string, cln *string, envExt ...string) (*lspBase, error) { +func newLspd(h *lntest.TestHarness, name string, nodeConfig *config.NodeConfig, lnd *config.LndConfig, cln *config.ClnConfig, envExt ...string) (*lspBase, error) { scriptDir := h.GetDirectory(fmt.Sprintf("lspd-%s", name)) log.Printf("%s: Creating LSPD in dir %s", name, scriptDir) @@ -88,32 +91,45 @@ func newLspd(h *lntest.TestHarness, name string, lnd *string, cln *string, envEx eciesPubl := ecies.NewPrivateKeyFromBytes(lspdPrivateKeyBytes).PublicKey host := "localhost" grpcAddress := fmt.Sprintf("%s:%d", host, lspdPort) - var ext string - if lnd != nil { - ext = fmt.Sprintf(`"lnd": %s`, *lnd) - } else if cln != nil { - ext = fmt.Sprintf(`"cln": %s`, *cln) - } else { - h.T.Fatalf("%s: need either lnd or cln config", name) + minConfs := uint32(1) + conf := &config.NodeConfig{ + Name: name, + LspdPrivateKey: hex.EncodeToString(lspdPrivateKeyBytes), + Token: "hello", + Host: "host:port", + PublicChannelAmount: 1000183, + ChannelAmount: 100000, + ChannelPrivate: false, + TargetConf: 6, + MinConfs: &minConfs, + MinHtlcMsat: 600, + BaseFeeMsat: 1000, + FeeRate: 0.000001, + TimeLockDelta: 144, + ChannelFeePermyriad: 40, + ChannelMinimumFeeMsat: 2000000, + AdditionalChannelCapacity: 100000, + MaxInactiveDuration: 3888000, + Lnd: lnd, + Cln: cln, } - nodes := fmt.Sprintf( - `NODES='[ { "name": "%s", "lspdPrivateKey": "%x", "token": "hello", "host": "host:port",`+ - ` "publicChannelAmount": "1000183", "channelAmount": "100000", "channelPrivate": false,`+ - ` "targetConf": "6", "minConfs": "1", "minHtlcMsat": "600", "baseFeeMsat": "1000", "feeRate": "0.000001",`+ - ` "timeLockDelta": "144", "channelFeePermyriad": "40", "channelMinimumFeeMsat": "2000000",`+ - ` "additionalChannelCapacity": "100000", "maxInactiveDuration": "3888000", %s}]'`, - name, - lspdPrivateKeyBytes, - ext, - ) + if nodeConfig != nil { + if nodeConfig.MinConfs != nil { + conf.MinConfs = nodeConfig.MinConfs + } + } + + log.Printf("%+v", conf) + confJson, _ := json.Marshal(conf) + nodes := fmt.Sprintf(`NODES='[%s]'`, string(confJson)) env := []string{ nodes, fmt.Sprintf("DATABASE_URL=%s", postgresBackend.ConnectionString()), fmt.Sprintf("LISTEN_ADDRESS=%s", grpcAddress), - fmt.Sprintf("USE_MEMPOOL_FEE_ESTIMATION=true"), - fmt.Sprintf("MEMPOOL_API_BASE_URL=https://mempool.space/api/v1/"), - fmt.Sprintf("MEMPOOL_PRIORITY=economy"), + "USE_MEMPOOL_FEE_ESTIMATION=true", + "MEMPOOL_API_BASE_URL=https://mempool.space/api/v1/", + "MEMPOOL_PRIORITY=economy", } env = append(env, envExt...) diff --git a/itest/lspd_test.go b/itest/lspd_test.go index f888f82..8c6c73b 100644 --- a/itest/lspd_test.go +++ b/itest/lspd_test.go @@ -7,31 +7,55 @@ import ( "time" "github.com/breez/lntest" + "github.com/breez/lspd/config" ) var defaultTimeout time.Duration = time.Second * 120 func TestLspd(t *testing.T) { testCases := allTestCases - runTests(t, testCases, "LND-lspd", func(h *lntest.TestHarness, m *lntest.Miner) (LspNode, BreezClient) { - return NewLndLspdNode(h, m, "lsp"), newLndBreezClient(h, m, "breez-client") - }) - - runTests(t, testCases, "CLN-lspd", func(h *lntest.TestHarness, m *lntest.Miner) (LspNode, BreezClient) { - return NewClnLspdNode(h, m, "lsp"), newClnBreezClient(h, m, "breez-client") - }) + runTests(t, testCases, "LND-lspd", lndLspFunc, lndClientFunc) + runTests(t, testCases, "CLN-lspd", clnLspFunc, clnClientFunc) } -func runTests(t *testing.T, testCases []*testCase, prefix string, nodesFunc func(h *lntest.TestHarness, m *lntest.Miner) (LspNode, BreezClient)) { +func lndLspFunc(h *lntest.TestHarness, m *lntest.Miner, c *config.NodeConfig) LspNode { + return NewLndLspdNode(h, m, "lsp", c) +} + +func clnLspFunc(h *lntest.TestHarness, m *lntest.Miner, c *config.NodeConfig) LspNode { + return NewClnLspdNode(h, m, "lsp", c) +} + +func lndClientFunc(h *lntest.TestHarness, m *lntest.Miner) BreezClient { + return newLndBreezClient(h, m, "breez-client") +} + +func clnClientFunc(h *lntest.TestHarness, m *lntest.Miner) BreezClient { + return newClnBreezClient(h, m, "breez-client") +} + +func runTests( + t *testing.T, + testCases []*testCase, + prefix string, + lspFunc LspFunc, + clientFunc ClientFunc, +) { for _, testCase := range testCases { testCase := testCase t.Run(fmt.Sprintf("%s: %s", prefix, testCase.name), func(t *testing.T) { - runTest(t, testCase, prefix, nodesFunc) + runTest(t, testCase, prefix, lspFunc, clientFunc) }) } } -func runTest(t *testing.T, testCase *testCase, prefix string, nodesFunc func(h *lntest.TestHarness, m *lntest.Miner) (LspNode, BreezClient)) { +func runTest( + t *testing.T, + testCase *testCase, + prefix string, + lspFunc LspFunc, + clientFunc ClientFunc, +) { log.Printf("%s: Running test case '%s'", prefix, testCase.name) var dd time.Duration to := testCase.timeout @@ -49,23 +73,30 @@ func runTest(t *testing.T, testCase *testCase, prefix string, nodesFunc func(h * miner := lntest.NewMiner(h) miner.Start() log.Printf("Creating lsp") - lsp, c := nodesFunc(h, miner) - lsp.Start() + var lsp LspNode + if !testCase.skipCreateLsp { + lsp = lspFunc(h, miner, nil) + lsp.Start() + } + c := clientFunc(h, miner) c.Start() log.Printf("Run testcase") testCase.test(&testParams{ - t: t, - h: h, - m: miner, - c: c, - lsp: lsp, + t: t, + h: h, + m: miner, + c: c, + lsp: lsp, + lspFunc: lspFunc, + clientFunc: clientFunc, }) } type testCase struct { - name string - test func(t *testParams) - timeout time.Duration + name string + test func(t *testParams) + skipCreateLsp bool + timeout time.Duration } var allTestCases = []*testCase{ @@ -105,4 +136,9 @@ var allTestCases = []*testCase{ name: "registerPaymentWithTag", test: registerPaymentWithTag, }, + { + name: "testOpenZeroConfUtxo", + test: testOpenZeroConfUtxo, + skipCreateLsp: true, + }, } diff --git a/itest/test_params.go b/itest/test_params.go index d67f708..a3d420d 100644 --- a/itest/test_params.go +++ b/itest/test_params.go @@ -4,14 +4,20 @@ import ( "testing" "github.com/breez/lntest" + "github.com/breez/lspd/config" ) +type LspFunc func(h *lntest.TestHarness, m *lntest.Miner, c *config.NodeConfig) LspNode +type ClientFunc func(h *lntest.TestHarness, m *lntest.Miner) BreezClient + type testParams struct { - t *testing.T - h *lntest.TestHarness - m *lntest.Miner - c BreezClient - lsp LspNode + t *testing.T + h *lntest.TestHarness + m *lntest.Miner + c BreezClient + lsp LspNode + lspFunc LspFunc + clientFunc ClientFunc } func (h *testParams) T() *testing.T { diff --git a/itest/zero_conf_utxo_test.go b/itest/zero_conf_utxo_test.go new file mode 100644 index 0000000..1733912 --- /dev/null +++ b/itest/zero_conf_utxo_test.go @@ -0,0 +1,72 @@ +package itest + +import ( + "log" + "time" + + "github.com/breez/lntest" + "github.com/breez/lspd/config" + lspd "github.com/breez/lspd/rpc" + "github.com/stretchr/testify/assert" +) + +func testOpenZeroConfUtxo(p *testParams) { + alice := lntest.NewClnNode(p.h, p.m, "Alice") + alice.Start() + alice.Fund(10000000) + + minConfs := uint32(0) + lsp := p.lspFunc(p.h, p.m, &config.NodeConfig{MinConfs: &minConfs}) + lsp.Start() + + log.Print("Opening channel between Alice and the lsp") + channel := alice.OpenChannel(lsp.LightningNode(), &lntest.OpenChannelOptions{ + AmountSat: publicChanAmount, + }) + channelId := alice.WaitForChannelReady(channel) + + // Send an unconfirmed utxo to the lsp + initialHeight := p.m.GetBlockHeight() + addr := lsp.LightningNode().GetNewAddress() + p.m.SendToAddress(addr, 200000) + + log.Printf("Adding bob's invoices") + outerAmountMsat := uint64(2100000) + innerAmountMsat, lspAmountMsat := calculateInnerAmountMsat(lsp, outerAmountMsat) + description := "Please pay me" + innerInvoice, outerInvoice := GenerateInvoices(p.BreezClient(), + generateInvoicesRequest{ + innerAmountMsat: innerAmountMsat, + outerAmountMsat: outerAmountMsat, + description: description, + lsp: lsp, + }) + + log.Print("Connecting bob to lspd") + p.BreezClient().Node().ConnectPeer(lsp.LightningNode()) + + log.Printf("Registering payment with lsp") + RegisterPayment(lsp, &lspd.PaymentInformation{ + PaymentHash: innerInvoice.paymentHash, + PaymentSecret: innerInvoice.paymentSecret, + Destination: p.BreezClient().Node().NodeId(), + IncomingAmountMsat: int64(outerAmountMsat), + OutgoingAmountMsat: int64(lspAmountMsat), + }) + + // TODO: Fix race waiting for htlc interceptor. + log.Printf("Waiting %v to allow htlc interceptor to activate.", htlcInterceptorDelay) + <-time.After(htlcInterceptorDelay) + log.Printf("Alice paying") + route := constructRoute(lsp.LightningNode(), p.BreezClient().Node(), channelId, lntest.NewShortChanIDFromString("1x0x0"), outerAmountMsat) + payResp, err := alice.PayViaRoute(outerAmountMsat, outerInvoice.paymentHash, outerInvoice.paymentSecret, route) + lntest.CheckError(p.t, err) + bobInvoice := p.BreezClient().Node().GetInvoice(payResp.PaymentHash) + + assert.Equal(p.t, payResp.PaymentPreimage, bobInvoice.PaymentPreimage) + assert.Equal(p.t, innerAmountMsat, bobInvoice.AmountReceivedMsat) + + // Make sure there's not accidently a block mined in between + finalHeight := p.m.GetBlockHeight() + assert.Equal(p.t, initialHeight, finalHeight) +} diff --git a/lightning_client.go b/lightning_client.go index 7260d85..f026bfe 100644 --- a/lightning_client.go +++ b/lightning_client.go @@ -21,7 +21,7 @@ type OpenChannelRequest struct { MinHtlcMsat uint64 IsPrivate bool IsZeroConf bool - MinConfs uint32 + MinConfs *uint32 FeeSatPerVByte *float64 TargetConf *uint32 } diff --git a/lnd_client.go b/lnd_client.go index d8f087e..cdf7e48 100644 --- a/lnd_client.go +++ b/lnd_client.go @@ -8,6 +8,7 @@ import ( "log" "github.com/breez/lspd/basetypes" + "github.com/breez/lspd/config" "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/htlcswitch/hop" "github.com/lightningnetwork/lnd/lnrpc" @@ -24,7 +25,7 @@ type LndClient struct { conn *grpc.ClientConn } -func NewLndClient(conf *LndConfig) (*LndClient, error) { +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) @@ -103,7 +104,14 @@ func (c *LndClient) OpenChannel(req *OpenChannelRequest) (*wire.OutPoint, error) Private: req.IsPrivate, CommitmentType: lnrpc.CommitmentType_ANCHORS, ZeroConf: req.IsZeroConf, - MinConfs: int32(req.MinConfs), + } + + if req.MinConfs != nil { + minConfs := *req.MinConfs + lnReq.MinConfs = int32(minConfs) + if minConfs == 0 { + lnReq.SpendUnconfirmed = true + } } if req.FeeSatPerVByte != nil { diff --git a/lnd_interceptor.go b/lnd_interceptor.go index c474e93..f1cf06d 100644 --- a/lnd_interceptor.go +++ b/lnd_interceptor.go @@ -7,6 +7,7 @@ import ( "sync" "time" + "github.com/breez/lspd/config" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/routerrpc" "google.golang.org/grpc/codes" @@ -14,7 +15,7 @@ import ( ) type LndHtlcInterceptor struct { - config *NodeConfig + config *config.NodeConfig client *LndClient stopRequested bool initWg sync.WaitGroup @@ -23,7 +24,7 @@ type LndHtlcInterceptor struct { cancel context.CancelFunc } -func NewLndHtlcInterceptor(conf *NodeConfig) (*LndHtlcInterceptor, error) { +func NewLndHtlcInterceptor(conf *config.NodeConfig) (*LndHtlcInterceptor, error) { if conf.Lnd == nil { return nil, fmt.Errorf("missing lnd configuration") } diff --git a/main.go b/main.go index 975cff3..4c5ad3f 100644 --- a/main.go +++ b/main.go @@ -11,6 +11,7 @@ import ( "syscall" "github.com/breez/lspd/chain" + "github.com/breez/lspd/config" "github.com/breez/lspd/mempool" "github.com/btcsuite/btcd/btcec/v2" ) @@ -26,7 +27,7 @@ func main() { } n := os.Getenv("NODES") - var nodes []*NodeConfig + var nodes []*config.NodeConfig err := json.Unmarshal([]byte(n), &nodes) if err != nil { log.Fatalf("failed to unmarshal NODES env: %v", err) diff --git a/server.go b/server.go index 0bb13c0..5d23c41 100644 --- a/server.go +++ b/server.go @@ -11,6 +11,7 @@ import ( "strings" "github.com/breez/lspd/btceclegacy" + "github.com/breez/lspd/config" lspdrpc "github.com/breez/lspd/rpc" ecies "github.com/ecies/go/v2" "github.com/golang/protobuf/proto" @@ -39,7 +40,7 @@ type server struct { type node struct { client LightningClient - nodeConfig *NodeConfig + nodeConfig *config.NodeConfig privateKey *btcec.PrivateKey publicKey *btcec.PublicKey eciesPrivateKey *ecies.PrivateKey @@ -259,7 +260,7 @@ func (s *server) CheckChannels(ctx context.Context, in *lspdrpc.Encrypted) (*lsp return &lspdrpc.Encrypted{Data: encrypted}, nil } -func NewGrpcServer(configs []*NodeConfig, address string, certmagicDomain string) (*server, error) { +func NewGrpcServer(configs []*config.NodeConfig, address string, certmagicDomain string) (*server, error) { if len(configs) == 0 { return nil, fmt.Errorf("no nodes supplied") } From 27e34f50d5b5d8f5e06ce8eee2f8c527d0b25435 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 5 Mar 2023 15:09:54 +0000 Subject: [PATCH 158/214] Bump github.com/aws/aws-sdk-go from 1.30.20 to 1.34.0 Bumps [github.com/aws/aws-sdk-go](https://github.com/aws/aws-sdk-go) from 1.30.20 to 1.34.0. - [Release notes](https://github.com/aws/aws-sdk-go/releases) - [Changelog](https://github.com/aws/aws-sdk-go/blob/v1.34.0/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-go/compare/v1.30.20...v1.34.0) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 83dd399..9e7c6a3 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/breez/lspd go 1.19 require ( - github.com/aws/aws-sdk-go v1.30.20 + github.com/aws/aws-sdk-go v1.34.0 github.com/breez/lntest v0.0.19 github.com/btcsuite/btcd v0.23.3 github.com/btcsuite/btcd/btcec/v2 v2.2.1 From 5023ee494e38dc4392823b8bf3e8e5d3c84104da Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Mon, 6 Mar 2023 09:47:21 +0100 Subject: [PATCH 159/214] unconfirmed utxo from internal wallet --- go.mod | 2 +- itest/lspd_node.go | 2 +- itest/zero_conf_utxo_test.go | 8 ++++++-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index f91db20..4085f37 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.19 require ( github.com/aws/aws-sdk-go v1.34.0 - github.com/breez/lntest v0.0.19 + github.com/breez/lntest v0.0.20 github.com/btcsuite/btcd v0.23.3 github.com/btcsuite/btcd/btcec/v2 v2.2.1 github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 diff --git a/itest/lspd_node.go b/itest/lspd_node.go index a8d2e70..b8c6186 100644 --- a/itest/lspd_node.go +++ b/itest/lspd_node.go @@ -120,7 +120,7 @@ func newLspd(h *lntest.TestHarness, name string, nodeConfig *config.NodeConfig, } } - log.Printf("%+v", conf) + log.Printf("%s: node config: %+v", name, conf) confJson, _ := json.Marshal(conf) nodes := fmt.Sprintf(`NODES='[%s]'`, string(confJson)) env := []string{ diff --git a/itest/zero_conf_utxo_test.go b/itest/zero_conf_utxo_test.go index 1733912..d366a65 100644 --- a/itest/zero_conf_utxo_test.go +++ b/itest/zero_conf_utxo_test.go @@ -25,10 +25,14 @@ func testOpenZeroConfUtxo(p *testParams) { }) channelId := alice.WaitForChannelReady(channel) - // Send an unconfirmed utxo to the lsp + tempaddr := lsp.LightningNode().GetNewAddress() + p.m.SendToAddress(tempaddr, 210000) + p.m.MineBlocks(6) + lsp.LightningNode().WaitForSync() + initialHeight := p.m.GetBlockHeight() addr := lsp.LightningNode().GetNewAddress() - p.m.SendToAddress(addr, 200000) + lsp.LightningNode().SendToAddress(addr, 200000) log.Printf("Adding bob's invoices") outerAmountMsat := uint64(2100000) From f81037298fcf95030143ad38e4f8453b0fd51de8 Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Fri, 24 Mar 2023 14:40:59 +0100 Subject: [PATCH 160/214] cleanup: move outpoint to basetypes package --- outpoint.go => basetypes/outpoint.go | 2 +- cln_client.go | 2 +- db.go | 3 ++- lnd_client.go | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) rename outpoint.go => basetypes/outpoint.go (95%) diff --git a/outpoint.go b/basetypes/outpoint.go similarity index 95% rename from outpoint.go rename to basetypes/outpoint.go index a25bf8b..cdfa979 100644 --- a/outpoint.go +++ b/basetypes/outpoint.go @@ -1,4 +1,4 @@ -package main +package basetypes import ( "log" diff --git a/cln_client.go b/cln_client.go index b04e99f..a9ee908 100644 --- a/cln_client.go +++ b/cln_client.go @@ -131,7 +131,7 @@ func (c *ClnClient) OpenChannel(req *OpenChannelRequest) (*wire.OutPoint, error) return nil, err } - channelPoint, err := NewOutPoint(fundingTxId[:], uint32(fundResult.FundingTxOutputNum)) + channelPoint, err := basetypes.NewOutPoint(fundingTxId[:], uint32(fundResult.FundingTxOutputNum)) if err != nil { log.Printf("CLN: NewOutPoint(%s, %d) error: %v", fundingTxId.String(), fundResult.FundingTxOutputNum, err) return nil, err diff --git a/db.go b/db.go index b0332ec..cdd2b43 100644 --- a/db.go +++ b/db.go @@ -6,6 +6,7 @@ import ( "log" "time" + "github.com/breez/lspd/basetypes" "github.com/btcsuite/btcd/wire" "github.com/jackc/pgtype" "github.com/jackc/pgx/v4" @@ -46,7 +47,7 @@ func paymentInfo(htlcPaymentHash []byte) ([]byte, []byte, []byte, int64, int64, var cp *wire.OutPoint if fundingTxID != nil { - cp, err = NewOutPoint(fundingTxID, uint32(fundingTxOutnum.Int)) + cp, err = basetypes.NewOutPoint(fundingTxID, uint32(fundingTxOutnum.Int)) if err != nil { log.Printf("invalid funding txid in database %x", fundingTxID) } diff --git a/lnd_client.go b/lnd_client.go index cdf7e48..113cb6a 100644 --- a/lnd_client.go +++ b/lnd_client.go @@ -126,7 +126,7 @@ func (c *LndClient) OpenChannel(req *OpenChannelRequest) (*wire.OutPoint, error) return nil, fmt.Errorf("LND: OpenChannel() error: %w", err) } - result, err := NewOutPoint(channelPoint.GetFundingTxidBytes(), channelPoint.OutputIndex) + result, err := basetypes.NewOutPoint(channelPoint.GetFundingTxidBytes(), channelPoint.OutputIndex) if err != nil { log.Printf("LND: OpenChannel returned invalid outpoint. error: %v", err) return nil, err From 9781ac6bb0ede83d8f3cecbbb3ad7c38b62afedf Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Fri, 24 Mar 2023 14:43:20 +0100 Subject: [PATCH 161/214] cleanup: move lightning client to seperate package --- cln_client.go | 11 ++++++----- intercept.go | 7 ++++--- lightning_client.go => lightning/client.go | 4 ++-- lnd_client.go | 11 ++++++----- server.go | 5 +++-- 5 files changed, 21 insertions(+), 17 deletions(-) rename lightning_client.go => lightning/client.go (94%) diff --git a/cln_client.go b/cln_client.go index a9ee908..0026151 100644 --- a/cln_client.go +++ b/cln_client.go @@ -7,6 +7,7 @@ import ( "path/filepath" "github.com/breez/lspd/basetypes" + "github.com/breez/lspd/lightning" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" "github.com/niftynei/glightning/glightning" @@ -42,14 +43,14 @@ func NewClnClient(socketPath string) (*ClnClient, error) { }, nil } -func (c *ClnClient) GetInfo() (*GetInfoResult, error) { +func (c *ClnClient) GetInfo() (*lightning.GetInfoResult, error) { info, err := c.client.GetInfo() if err != nil { log.Printf("CLN: client.GetInfo() error: %v", err) return nil, err } - return &GetInfoResult{ + return &lightning.GetInfoResult{ Alias: info.Alias, Pubkey: info.Id, }, nil @@ -74,7 +75,7 @@ func (c *ClnClient) IsConnected(destination []byte) (bool, error) { return false, nil } -func (c *ClnClient) OpenChannel(req *OpenChannelRequest) (*wire.OutPoint, error) { +func (c *ClnClient) OpenChannel(req *lightning.OpenChannelRequest) (*wire.OutPoint, error) { pubkey := hex.EncodeToString(req.Destination) var minConfs *uint16 if req.MinConfs != nil { @@ -140,7 +141,7 @@ func (c *ClnClient) OpenChannel(req *OpenChannelRequest) (*wire.OutPoint, error) return channelPoint, nil } -func (c *ClnClient) GetChannel(peerID []byte, channelPoint wire.OutPoint) (*GetChannelResult, error) { +func (c *ClnClient) GetChannel(peerID []byte, channelPoint wire.OutPoint) (*lightning.GetChannelResult, error) { pubkey := hex.EncodeToString(peerID) peer, err := c.client.GetPeer(pubkey) if err != nil { @@ -162,7 +163,7 @@ func (c *ClnClient) GetChannel(peerID []byte, channelPoint wire.OutPoint) (*GetC fmt.Printf("NewShortChannelIDFromString %v error: %v", c.Alias.Local, err) return nil, err } - return &GetChannelResult{ + return &lightning.GetChannelResult{ InitialChannelID: *initialChanID, ConfirmedChannelID: *confirmedChanID, }, nil diff --git a/intercept.go b/intercept.go index 2683e21..52b6cce 100644 --- a/intercept.go +++ b/intercept.go @@ -11,6 +11,7 @@ import ( "github.com/breez/lspd/chain" "github.com/breez/lspd/config" + "github.com/breez/lspd/lightning" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/wire" sphinx "github.com/lightningnetwork/lightning-onion" @@ -50,7 +51,7 @@ type interceptResult struct { onionBlob []byte } -func intercept(client LightningClient, config *config.NodeConfig, nextHop string, reqPaymentHash []byte, reqOutgoingAmountMsat uint64, reqOutgoingExpiry uint32, reqIncomingExpiry uint32) interceptResult { +func intercept(client lightning.Client, config *config.NodeConfig, nextHop string, reqPaymentHash []byte, reqOutgoingAmountMsat uint64, reqOutgoingExpiry uint32, reqIncomingExpiry uint32) interceptResult { reqPaymentHashStr := hex.EncodeToString(reqPaymentHash) resp, _, _ := payHashGroup.Do(reqPaymentHashStr, func() (interface{}, error) { paymentHash, paymentSecret, destination, incomingAmountMsat, outgoingAmountMsat, channelPoint, err := paymentInfo(reqPaymentHash) @@ -236,7 +237,7 @@ func checkPayment(config *config.NodeConfig, incomingAmountMsat, outgoingAmountM return nil } -func openChannel(client LightningClient, config *config.NodeConfig, paymentHash, destination []byte, incomingAmountMsat int64) (*wire.OutPoint, error) { +func openChannel(client lightning.Client, config *config.NodeConfig, paymentHash, destination []byte, incomingAmountMsat int64) (*wire.OutPoint, error) { capacity := incomingAmountMsat/1000 + config.AdditionalChannelCapacity if capacity == config.PublicChannelAmount { capacity++ @@ -268,7 +269,7 @@ func openChannel(client LightningClient, config *config.NodeConfig, paymentHash, feeStr, confStr, ) - channelPoint, err := client.OpenChannel(&OpenChannelRequest{ + channelPoint, err := client.OpenChannel(&lightning.OpenChannelRequest{ Destination: destination, CapacitySat: uint64(capacity), MinConfs: config.MinConfs, diff --git a/lightning_client.go b/lightning/client.go similarity index 94% rename from lightning_client.go rename to lightning/client.go index f026bfe..d414deb 100644 --- a/lightning_client.go +++ b/lightning/client.go @@ -1,4 +1,4 @@ -package main +package lightning import ( "github.com/breez/lspd/basetypes" @@ -26,7 +26,7 @@ type OpenChannelRequest struct { TargetConf *uint32 } -type LightningClient interface { +type Client interface { GetInfo() (*GetInfoResult, error) IsConnected(destination []byte) (bool, error) OpenChannel(req *OpenChannelRequest) (*wire.OutPoint, error) diff --git a/lnd_client.go b/lnd_client.go index 113cb6a..1ced705 100644 --- a/lnd_client.go +++ b/lnd_client.go @@ -9,6 +9,7 @@ import ( "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" @@ -64,14 +65,14 @@ func (c *LndClient) Close() { c.conn.Close() } -func (c *LndClient) GetInfo() (*GetInfoResult, error) { +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 &GetInfoResult{ + return &lightning.GetInfoResult{ Alias: info.Alias, Pubkey: info.IdentityPubkey, }, nil @@ -96,7 +97,7 @@ func (c *LndClient) IsConnected(destination []byte) (bool, error) { return false, nil } -func (c *LndClient) OpenChannel(req *OpenChannelRequest) (*wire.OutPoint, error) { +func (c *LndClient) OpenChannel(req *lightning.OpenChannelRequest) (*wire.OutPoint, error) { lnReq := &lnrpc.OpenChannelRequest{ NodePubkey: req.Destination, LocalFundingAmount: int64(req.CapacitySat), @@ -135,7 +136,7 @@ func (c *LndClient) OpenChannel(req *OpenChannelRequest) (*wire.OutPoint, error) return result, nil } -func (c *LndClient) GetChannel(peerID []byte, channelPoint wire.OutPoint) (*GetChannelResult, error) { +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) @@ -157,7 +158,7 @@ func (c *LndClient) GetChannel(peerID []byte, channelPoint wire.OutPoint) (*GetC confirmedChanId = 0 } } - return &GetChannelResult{ + return &lightning.GetChannelResult{ InitialChannelID: basetypes.ShortChannelID(c.ChanId), ConfirmedChannelID: basetypes.ShortChannelID(confirmedChanId), }, nil diff --git a/server.go b/server.go index 5d23c41..f14152f 100644 --- a/server.go +++ b/server.go @@ -12,6 +12,7 @@ import ( "github.com/breez/lspd/btceclegacy" "github.com/breez/lspd/config" + "github.com/breez/lspd/lightning" lspdrpc "github.com/breez/lspd/rpc" ecies "github.com/ecies/go/v2" "github.com/golang/protobuf/proto" @@ -39,7 +40,7 @@ type server struct { } type node struct { - client LightningClient + client lightning.Client nodeConfig *config.NodeConfig privateKey *btcec.PrivateKey publicKey *btcec.PublicKey @@ -140,7 +141,7 @@ func (s *server) OpenChannel(ctx context.Context, in *lspdrpc.OpenChannelRequest var outPoint *wire.OutPoint if channelCount == 0 { - outPoint, err = node.client.OpenChannel(&OpenChannelRequest{ + outPoint, err = node.client.OpenChannel(&lightning.OpenChannelRequest{ CapacitySat: node.nodeConfig.ChannelAmount, Destination: pubkey, TargetConf: &node.nodeConfig.TargetConf, From 086d500750b24a283e59dfa548acdccedb6e442c Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Fri, 24 Mar 2023 15:53:19 +0100 Subject: [PATCH 162/214] cleanup: convert intercept and database to types --- cln_interceptor.go | 13 +--- forwarding_history.go | 44 ++++++++++---- intercept.go | 56 +++++++++-------- interceptor/store.go | 14 +++++ lnd/forwarding_event_store.go | 12 ++++ lnd_interceptor.go | 28 +++++---- main.go | 41 +++++++++---- postgresql/connect.go | 17 ++++++ postgresql/forwarding_event_store.go | 68 +++++++++++++++++++++ db.go => postgresql/intercept_store.go | 84 +++++--------------------- server.go | 23 ++++++- 11 files changed, 258 insertions(+), 142 deletions(-) create mode 100644 interceptor/store.go create mode 100644 lnd/forwarding_event_store.go create mode 100644 postgresql/connect.go create mode 100644 postgresql/forwarding_event_store.go rename db.go => postgresql/intercept_store.go (57%) diff --git a/cln_interceptor.go b/cln_interceptor.go index bc7b491..cce2c94 100644 --- a/cln_interceptor.go +++ b/cln_interceptor.go @@ -24,6 +24,7 @@ import ( ) type ClnHtlcInterceptor struct { + interceptor *Interceptor config *config.NodeConfig pluginAddress string client *ClnClient @@ -35,15 +36,7 @@ type ClnHtlcInterceptor struct { cancel context.CancelFunc } -func NewClnHtlcInterceptor(conf *config.NodeConfig) (*ClnHtlcInterceptor, error) { - if conf.Cln == nil { - return nil, fmt.Errorf("missing cln config") - } - - client, err := NewClnClient(conf.Cln.SocketPath) - if err != nil { - return nil, err - } +func NewClnHtlcInterceptor(conf *config.NodeConfig, client *ClnClient, interceptor *Interceptor) (*ClnHtlcInterceptor, error) { i := &ClnHtlcInterceptor{ config: conf, pluginAddress: conf.Cln.PluginAddress, @@ -169,7 +162,7 @@ func (i *ClnHtlcInterceptor) intercept() error { interceptorClient.Send(i.defaultResolution(request)) i.doneWg.Done() } - interceptResult := intercept(i.client, i.config, nextHop, paymentHash, request.Onion.ForwardMsat, request.Onion.OutgoingCltvValue, request.Htlc.CltvExpiry) + interceptResult := i.interceptor.Intercept(nextHop, paymentHash, request.Onion.ForwardMsat, request.Onion.OutgoingCltvValue, request.Htlc.CltvExpiry) switch interceptResult.action { case INTERCEPT_RESUME_WITH_ONION: interceptorClient.Send(i.resumeWithOnion(request, interceptResult)) diff --git a/forwarding_history.go b/forwarding_history.go index e45166d..1fa960d 100644 --- a/forwarding_history.go +++ b/forwarding_history.go @@ -7,6 +7,8 @@ import ( "log" "time" + "github.com/breez/lspd/interceptor" + "github.com/breez/lspd/lnd" "github.com/lightningnetwork/lnd/htlcswitch/hop" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/chainrpc" @@ -36,14 +38,32 @@ func (cfe *copyFromEvents) Err() error { return cfe.err } -func channelsSynchronize(ctx context.Context, client *LndClient) { +type ForwardingHistorySync struct { + client *LndClient + interceptStore interceptor.InterceptStore + forwardingStore lnd.ForwardingEventStore +} + +func NewForwardingHistorySync( + client *LndClient, + interceptStore interceptor.InterceptStore, + forwardingStore lnd.ForwardingEventStore, +) *ForwardingHistorySync { + return &ForwardingHistorySync{ + client: client, + interceptStore: interceptStore, + forwardingStore: forwardingStore, + } +} + +func (s *ForwardingHistorySync) ChannelsSynchronize(ctx context.Context) { lastSync := time.Now().Add(-6 * time.Minute) for { if ctx.Err() != nil { return } - stream, err := client.chainNotifierClient.RegisterBlockEpochNtfn(ctx, &chainrpc.BlockEpoch{}) + stream, err := s.client.chainNotifierClient.RegisterBlockEpochNtfn(ctx, &chainrpc.BlockEpoch{}) if err != nil { log.Printf("chainNotifierClient.RegisterBlockEpochNtfn(): %v", err) <-time.After(time.Second) @@ -67,7 +87,7 @@ func channelsSynchronize(ctx context.Context, client *LndClient) { return case <-time.After(1 * time.Minute): } - err = channelsSynchronizeOnce(client) + err = s.ChannelsSynchronizeOnce() lastSync = time.Now() log.Printf("channelsSynchronizeOnce() err: %v", err) } @@ -75,9 +95,9 @@ func channelsSynchronize(ctx context.Context, client *LndClient) { } } -func channelsSynchronizeOnce(client *LndClient) error { +func (s *ForwardingHistorySync) ChannelsSynchronizeOnce() error { log.Printf("channelsSynchronizeOnce - begin") - channels, err := client.client.ListChannels(context.Background(), &lnrpc.ListChannelsRequest{PrivateOnly: true}) + channels, err := s.client.client.ListChannels(context.Background(), &lnrpc.ListChannelsRequest{PrivateOnly: true}) if err != nil { log.Printf("ListChannels error: %v", err) return fmt.Errorf("client.ListChannels() error: %w", err) @@ -97,7 +117,7 @@ func channelsSynchronizeOnce(client *LndClient) error { confirmedChanId = 0 } } - err = insertChannel(c.ChanId, confirmedChanId, c.ChannelPoint, nodeID, lastUpdate) + err = s.interceptStore.InsertChannel(c.ChanId, confirmedChanId, c.ChannelPoint, nodeID, lastUpdate) if err != nil { log.Printf("insertChannel(%v, %v, %x) in channelsSynchronizeOnce error: %v", c.ChanId, c.ChannelPoint, nodeID, err) continue @@ -108,13 +128,13 @@ func channelsSynchronizeOnce(client *LndClient) error { return nil } -func forwardingHistorySynchronize(ctx context.Context, client *LndClient) { +func (s *ForwardingHistorySync) ForwardingHistorySynchronize(ctx context.Context) { for { if ctx.Err() != nil { return } - err := forwardingHistorySynchronizeOnce(client) + err := s.ForwardingHistorySynchronizeOnce() log.Printf("forwardingHistorySynchronizeOnce() err: %v", err) select { case <-time.After(1 * time.Minute): @@ -123,8 +143,8 @@ func forwardingHistorySynchronize(ctx context.Context, client *LndClient) { } } -func forwardingHistorySynchronizeOnce(client *LndClient) error { - last, err := lastForwardingEvent() +func (s *ForwardingHistorySync) ForwardingHistorySynchronizeOnce() error { + last, err := s.forwardingStore.LastForwardingEvent() if err != nil { return fmt.Errorf("lastForwardingEvent() error: %w", err) } @@ -138,7 +158,7 @@ func forwardingHistorySynchronizeOnce(client *LndClient) error { endTime := uint64(now.Add(time.Hour * 24).Unix()) indexOffset := uint32(0) for { - forwardHistory, err := client.client.ForwardingHistory(context.Background(), &lnrpc.ForwardingHistoryRequest{ + forwardHistory, err := s.client.client.ForwardingHistory(context.Background(), &lnrpc.ForwardingHistoryRequest{ StartTime: uint64(last), EndTime: endTime, NumMaxEvents: 10000, @@ -154,7 +174,7 @@ func forwardingHistorySynchronizeOnce(client *LndClient) error { } indexOffset = forwardHistory.LastOffsetIndex cfe := copyFromEvents{events: forwardHistory.ForwardingEvents, idx: -1} - err = insertForwardingEvents(&cfe) + err = s.forwardingStore.InsertForwardingEvents(&cfe) if err != nil { log.Printf("insertForwardingEvents() error: %v", err) return fmt.Errorf("insertForwardingEvents() error: %w", err) diff --git a/intercept.go b/intercept.go index 52b6cce..1460a2b 100644 --- a/intercept.go +++ b/intercept.go @@ -11,6 +11,7 @@ import ( "github.com/breez/lspd/chain" "github.com/breez/lspd/config" + "github.com/breez/lspd/interceptor" "github.com/breez/lspd/lightning" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/wire" @@ -51,10 +52,28 @@ type interceptResult struct { onionBlob []byte } -func intercept(client lightning.Client, config *config.NodeConfig, nextHop string, reqPaymentHash []byte, reqOutgoingAmountMsat uint64, reqOutgoingExpiry uint32, reqIncomingExpiry uint32) interceptResult { +type Interceptor struct { + client lightning.Client + config *config.NodeConfig + store interceptor.InterceptStore +} + +func NewInterceptor( + client lightning.Client, + config *config.NodeConfig, + store interceptor.InterceptStore, +) *Interceptor { + return &Interceptor{ + client: client, + config: config, + store: store, + } +} + +func (i *Interceptor) Intercept(nextHop string, reqPaymentHash []byte, reqOutgoingAmountMsat uint64, reqOutgoingExpiry uint32, reqIncomingExpiry uint32) interceptResult { reqPaymentHashStr := hex.EncodeToString(reqPaymentHash) resp, _, _ := payHashGroup.Do(reqPaymentHashStr, func() (interface{}, error) { - paymentHash, paymentSecret, destination, incomingAmountMsat, outgoingAmountMsat, channelPoint, err := paymentInfo(reqPaymentHash) + paymentHash, paymentSecret, destination, incomingAmountMsat, outgoingAmountMsat, channelPoint, err := i.store.PaymentInfo(reqPaymentHash) if err != nil { log.Printf("paymentInfo(%x) error: %v", reqPaymentHash, err) return interceptResult{ @@ -72,14 +91,14 @@ func intercept(client lightning.Client, config *config.NodeConfig, nextHop strin if channelPoint == nil { if bytes.Equal(paymentHash, reqPaymentHash) { - if int64(reqIncomingExpiry)-int64(reqOutgoingExpiry) < int64(config.TimeLockDelta) { + if int64(reqIncomingExpiry)-int64(reqOutgoingExpiry) < int64(i.config.TimeLockDelta) { return interceptResult{ action: INTERCEPT_FAIL_HTLC_WITH_CODE, failureCode: FAILURE_TEMPORARY_CHANNEL_FAILURE, }, nil } - channelPoint, err = openChannel(client, config, reqPaymentHash, destination, incomingAmountMsat) + channelPoint, err = i.openChannel(reqPaymentHash, destination, incomingAmountMsat) if err != nil { log.Printf("openChannel(%x, %v) err: %v", destination, incomingAmountMsat, err) return interceptResult{ @@ -173,11 +192,11 @@ func intercept(client lightning.Client, config *config.NodeConfig, nextHop strin deadline := time.Now().Add(60 * time.Second) for { - chanResult, _ := client.GetChannel(destination, *channelPoint) + chanResult, _ := i.client.GetChannel(destination, *channelPoint) if chanResult != nil { log.Printf("channel opended successfully alias: %v, confirmed: %v", chanResult.InitialChannelID.ToString(), chanResult.ConfirmedChannelID.ToString()) - err := insertChannel( + err := i.store.InsertChannel( uint64(chanResult.InitialChannelID), uint64(chanResult.ConfirmedChannelID), channelPoint.String(), @@ -226,20 +245,9 @@ func intercept(client lightning.Client, config *config.NodeConfig, nextHop strin return resp.(interceptResult) } -func checkPayment(config *config.NodeConfig, incomingAmountMsat, outgoingAmountMsat int64) error { - fees := incomingAmountMsat * config.ChannelFeePermyriad / 10_000 / 1_000 * 1_000 - if fees < config.ChannelMinimumFeeMsat { - fees = config.ChannelMinimumFeeMsat - } - if incomingAmountMsat-outgoingAmountMsat < fees { - return fmt.Errorf("not enough fees") - } - return nil -} - -func openChannel(client lightning.Client, config *config.NodeConfig, paymentHash, destination []byte, incomingAmountMsat int64) (*wire.OutPoint, error) { - capacity := incomingAmountMsat/1000 + config.AdditionalChannelCapacity - if capacity == config.PublicChannelAmount { +func (i *Interceptor) openChannel(paymentHash, destination []byte, incomingAmountMsat int64) (*wire.OutPoint, error) { + capacity := incomingAmountMsat/1000 + i.config.AdditionalChannelCapacity + if capacity == i.config.PublicChannelAmount { capacity++ } @@ -257,7 +265,7 @@ func openChannel(client lightning.Client, config *config.NodeConfig, paymentHash feeStr = fmt.Sprintf("%.5f", *feeEstimation) } else { log.Printf("Error estimating chain fee, fallback to target conf: %v", err) - targetConf = &config.TargetConf + targetConf = &i.config.TargetConf confStr = fmt.Sprintf("%v", *targetConf) } } @@ -269,10 +277,10 @@ func openChannel(client lightning.Client, config *config.NodeConfig, paymentHash feeStr, confStr, ) - channelPoint, err := client.OpenChannel(&lightning.OpenChannelRequest{ + channelPoint, err := i.client.OpenChannel(&lightning.OpenChannelRequest{ Destination: destination, CapacitySat: uint64(capacity), - MinConfs: config.MinConfs, + MinConfs: i.config.MinConfs, IsPrivate: true, IsZeroConf: true, FeeSatPerVByte: feeEstimation, @@ -289,6 +297,6 @@ func openChannel(client lightning.Client, config *config.NodeConfig, paymentHash capacity, channelPoint.String(), ) - err = setFundingTx(paymentHash, channelPoint) + err = i.store.SetFundingTx(paymentHash, channelPoint) return channelPoint, err } diff --git a/interceptor/store.go b/interceptor/store.go new file mode 100644 index 0000000..3956ec2 --- /dev/null +++ b/interceptor/store.go @@ -0,0 +1,14 @@ +package interceptor + +import ( + "time" + + "github.com/btcsuite/btcd/wire" +) + +type InterceptStore interface { + PaymentInfo(htlcPaymentHash []byte) ([]byte, []byte, []byte, int64, int64, *wire.OutPoint, error) + SetFundingTx(paymentHash []byte, channelPoint *wire.OutPoint) error + RegisterPayment(destination, paymentHash, paymentSecret []byte, incomingAmountMsat, outgoingAmountMsat int64, tag string) error + InsertChannel(initialChanID, confirmedChanId uint64, channelPoint string, nodeID []byte, lastUpdate time.Time) error +} diff --git a/lnd/forwarding_event_store.go b/lnd/forwarding_event_store.go new file mode 100644 index 0000000..22fc4a4 --- /dev/null +++ b/lnd/forwarding_event_store.go @@ -0,0 +1,12 @@ +package lnd + +type CopyFromSource interface { + Next() bool + Values() ([]interface{}, error) + Err() error +} + +type ForwardingEventStore interface { + LastForwardingEvent() (int64, error) + InsertForwardingEvents(rowSrc CopyFromSource) error +} diff --git a/lnd_interceptor.go b/lnd_interceptor.go index f1cf06d..ca84cd8 100644 --- a/lnd_interceptor.go +++ b/lnd_interceptor.go @@ -15,6 +15,8 @@ import ( ) type LndHtlcInterceptor struct { + fwsync *ForwardingHistorySync + interceptor *Interceptor config *config.NodeConfig client *LndClient stopRequested bool @@ -24,17 +26,17 @@ type LndHtlcInterceptor struct { cancel context.CancelFunc } -func NewLndHtlcInterceptor(conf *config.NodeConfig) (*LndHtlcInterceptor, error) { - if conf.Lnd == nil { - return nil, fmt.Errorf("missing lnd configuration") - } - client, err := NewLndClient(conf.Lnd) - if err != nil { - return nil, err - } +func NewLndHtlcInterceptor( + conf *config.NodeConfig, + client *LndClient, + fwsync *ForwardingHistorySync, + interceptor *Interceptor, +) (*LndHtlcInterceptor, error) { i := &LndHtlcInterceptor{ - config: conf, - client: client, + config: conf, + client: client, + fwsync: fwsync, + interceptor: interceptor, } i.initWg.Add(1) @@ -47,8 +49,8 @@ func (i *LndHtlcInterceptor) Start() error { i.ctx = ctx i.cancel = cancel i.stopRequested = false - go forwardingHistorySynchronize(ctx, i.client) - go channelsSynchronize(ctx, i.client) + go i.fwsync.ForwardingHistorySynchronize(ctx) + go i.fwsync.ChannelsSynchronize(ctx) return i.intercept() } @@ -149,7 +151,7 @@ func (i *LndHtlcInterceptor) intercept() error { i.doneWg.Add(1) go func() { - interceptResult := intercept(i.client, i.config, nextHop, request.PaymentHash, request.OutgoingAmountMsat, request.OutgoingExpiry, request.IncomingExpiry) + interceptResult := i.interceptor.Intercept(nextHop, request.PaymentHash, request.OutgoingAmountMsat, request.OutgoingExpiry, request.IncomingExpiry) switch interceptResult.action { case INTERCEPT_RESUME_WITH_ONION: interceptorClient.Send(&routerrpc.ForwardHtlcInterceptResponse{ diff --git a/main.go b/main.go index 4c5ad3f..1f40262 100644 --- a/main.go +++ b/main.go @@ -13,6 +13,7 @@ import ( "github.com/breez/lspd/chain" "github.com/breez/lspd/config" "github.com/breez/lspd/mempool" + "github.com/breez/lspd/postgresql" "github.com/btcsuite/btcd/btcec/v2" ) @@ -63,43 +64,59 @@ func main() { log.Printf("using mempool api for fee estimation: %v, fee strategy: %v:%v", mempoolUrl, envFeeStrategy, feeStrategy) } + databaseUrl := os.Getenv("DATABASE_URL") + pool, err := postgresql.PgConnect(databaseUrl) + if err != nil { + log.Fatalf("pgConnect() error: %v", err) + } + + interceptStore := postgresql.NewPostgresInterceptStore(pool) + forwardingStore := postgresql.NewForwardingEventStore(pool) + var interceptors []HtlcInterceptor for _, node := range nodes { - var interceptor HtlcInterceptor + var htlcInterceptor HtlcInterceptor if node.Lnd != nil { - interceptor, err = NewLndHtlcInterceptor(node) + client, err := NewLndClient(node.Lnd) + if err != nil { + log.Fatalf("failed to initialize LND client: %v", err) + } + + fwsync := NewForwardingHistorySync(client, interceptStore, forwardingStore) + interceptor := NewInterceptor(client, node, interceptStore) + htlcInterceptor, err = NewLndHtlcInterceptor(node, client, fwsync, interceptor) if err != nil { log.Fatalf("failed to initialize LND interceptor: %v", err) } } if node.Cln != nil { - interceptor, err = NewClnHtlcInterceptor(node) + client, err := NewClnClient(node.Cln.SocketPath) + if err != nil { + log.Fatalf("failed to initialize CLN client: %v", err) + } + + interceptor := NewInterceptor(client, node, interceptStore) + htlcInterceptor, err = NewClnHtlcInterceptor(node, client, interceptor) if err != nil { log.Fatalf("failed to initialize CLN interceptor: %v", err) } } - if interceptor == nil { + if htlcInterceptor == nil { log.Fatalf("node has to be either cln or lnd") } - interceptors = append(interceptors, interceptor) + interceptors = append(interceptors, htlcInterceptor) } address := os.Getenv("LISTEN_ADDRESS") certMagicDomain := os.Getenv("CERTMAGIC_DOMAIN") - s, err := NewGrpcServer(nodes, address, certMagicDomain) + s, err := NewGrpcServer(nodes, address, certMagicDomain, interceptStore) if err != nil { log.Fatalf("failed to initialize grpc server: %v", err) } - databaseUrl := os.Getenv("DATABASE_URL") - err = pgConnect(databaseUrl) - if err != nil { - log.Fatalf("pgConnect() error: %v", err) - } - var wg sync.WaitGroup wg.Add(len(interceptors) + 1) diff --git a/postgresql/connect.go b/postgresql/connect.go new file mode 100644 index 0000000..b14942f --- /dev/null +++ b/postgresql/connect.go @@ -0,0 +1,17 @@ +package postgresql + +import ( + "context" + "fmt" + + "github.com/jackc/pgx/v4/pgxpool" +) + +func PgConnect(databaseUrl string) (*pgxpool.Pool, error) { + var err error + pgxPool, err := pgxpool.Connect(context.Background(), databaseUrl) + if err != nil { + return nil, fmt.Errorf("pgxpool.Connect(%v): %w", databaseUrl, err) + } + return pgxPool, nil +} diff --git a/postgresql/forwarding_event_store.go b/postgresql/forwarding_event_store.go new file mode 100644 index 0000000..42cda1c --- /dev/null +++ b/postgresql/forwarding_event_store.go @@ -0,0 +1,68 @@ +package postgresql + +import ( + "context" + "fmt" + "log" + + "github.com/breez/lspd/lnd" + "github.com/jackc/pgx/v4" + "github.com/jackc/pgx/v4/pgxpool" +) + +type ForwardingEventStore struct { + pool *pgxpool.Pool +} + +func NewForwardingEventStore(pool *pgxpool.Pool) *ForwardingEventStore { + return &ForwardingEventStore{pool: pool} +} + +func (s *ForwardingEventStore) LastForwardingEvent() (int64, error) { + var last int64 + err := s.pool.QueryRow(context.Background(), + `SELECT coalesce(MAX("timestamp"), 0) AS last FROM forwarding_history`).Scan(&last) + if err != nil { + return 0, err + } + return last, nil +} + +func (s *ForwardingEventStore) InsertForwardingEvents(rowSrc lnd.CopyFromSource) error { + + tx, err := s.pool.Begin(context.Background()) + if err != nil { + return fmt.Errorf("pgxPool.Begin() error: %w", err) + } + defer tx.Rollback(context.Background()) + + _, err = tx.Exec(context.Background(), ` + CREATE TEMP TABLE tmp_table ON COMMIT DROP AS + SELECT * + FROM forwarding_history + WITH NO DATA; + `) + if err != nil { + return fmt.Errorf("CREATE TEMP TABLE error: %w", err) + } + + count, err := tx.CopyFrom(context.Background(), + pgx.Identifier{"tmp_table"}, + []string{"timestamp", "chanid_in", "chanid_out", "amt_msat_in", "amt_msat_out"}, rowSrc) + if err != nil { + return fmt.Errorf("CopyFrom() error: %w", err) + } + log.Printf("count1: %v", count) + + cmdTag, err := tx.Exec(context.Background(), ` + INSERT INTO forwarding_history + SELECT * + FROM tmp_table + ON CONFLICT DO NOTHING + `) + if err != nil { + return fmt.Errorf("INSERT INTO forwarding_history error: %w", err) + } + log.Printf("count2: %v", cmdTag.RowsAffected()) + return tx.Commit(context.Background()) +} diff --git a/db.go b/postgresql/intercept_store.go similarity index 57% rename from db.go rename to postgresql/intercept_store.go index cdd2b43..c3dec86 100644 --- a/db.go +++ b/postgresql/intercept_store.go @@ -1,4 +1,4 @@ -package main +package postgresql import ( "context" @@ -13,27 +13,22 @@ import ( "github.com/jackc/pgx/v4/pgxpool" ) -var ( - pgxPool *pgxpool.Pool -) - -func pgConnect(databaseUrl string) error { - var err error - pgxPool, err = pgxpool.Connect(context.Background(), databaseUrl) - if err != nil { - return fmt.Errorf("pgxpool.Connect(%v): %w", databaseUrl, err) - } - return nil +type PostgresInterceptStore struct { + pool *pgxpool.Pool } -func paymentInfo(htlcPaymentHash []byte) ([]byte, []byte, []byte, int64, int64, *wire.OutPoint, error) { +func NewPostgresInterceptStore(pool *pgxpool.Pool) *PostgresInterceptStore { + return &PostgresInterceptStore{pool: pool} +} + +func (s *PostgresInterceptStore) PaymentInfo(htlcPaymentHash []byte) ([]byte, []byte, []byte, int64, int64, *wire.OutPoint, error) { var ( paymentHash, paymentSecret, destination []byte incomingAmountMsat, outgoingAmountMsat int64 fundingTxID []byte fundingTxOutnum pgtype.Int4 ) - err := pgxPool.QueryRow(context.Background(), + err := s.pool.QueryRow(context.Background(), `SELECT payment_hash, payment_secret, destination, incoming_amount_msat, outgoing_amount_msat, funding_tx_id, funding_tx_outnum FROM payments WHERE payment_hash=$1 OR sha256('probing-01:' || payment_hash)=$1`, @@ -55,8 +50,8 @@ func paymentInfo(htlcPaymentHash []byte) ([]byte, []byte, []byte, int64, int64, return paymentHash, paymentSecret, destination, incomingAmountMsat, outgoingAmountMsat, cp, nil } -func setFundingTx(paymentHash []byte, channelPoint *wire.OutPoint) error { - commandTag, err := pgxPool.Exec(context.Background(), +func (s *PostgresInterceptStore) SetFundingTx(paymentHash []byte, channelPoint *wire.OutPoint) error { + commandTag, err := s.pool.Exec(context.Background(), `UPDATE payments SET funding_tx_id = $2, funding_tx_outnum = $3 WHERE payment_hash=$1`, @@ -65,12 +60,12 @@ func setFundingTx(paymentHash []byte, channelPoint *wire.OutPoint) error { return err } -func registerPayment(destination, paymentHash, paymentSecret []byte, incomingAmountMsat, outgoingAmountMsat int64, tag string) error { +func (s *PostgresInterceptStore) RegisterPayment(destination, paymentHash, paymentSecret []byte, incomingAmountMsat, outgoingAmountMsat int64, tag string) error { var t *string if tag != "" { t = &tag } - commandTag, err := pgxPool.Exec(context.Background(), + commandTag, err := s.pool.Exec(context.Background(), `INSERT INTO payments (destination, payment_hash, payment_secret, incoming_amount_msat, outgoing_amount_msat, tag) VALUES ($1, $2, $3, $4, $5, $6) @@ -85,14 +80,14 @@ func registerPayment(destination, paymentHash, paymentSecret []byte, incomingAmo return nil } -func insertChannel(initialChanID, confirmedChanId uint64, channelPoint string, nodeID []byte, lastUpdate time.Time) error { +func (s *PostgresInterceptStore) InsertChannel(initialChanID, confirmedChanId uint64, channelPoint string, nodeID []byte, lastUpdate time.Time) error { query := `INSERT INTO channels (initial_chanid, confirmed_chanid, channel_point, nodeid, last_update) VALUES ($1, NULLIF($2, 0::int8), $3, $4, $5) ON CONFLICT (channel_point) DO UPDATE SET confirmed_chanid=NULLIF($2, 0::int8), last_update=$5` - c, err := pgxPool.Exec(context.Background(), + c, err := s.pool.Exec(context.Background(), query, int64(initialChanID), int64(confirmedChanId), channelPoint, nodeID, lastUpdate) if err != nil { log.Printf("insertChannel(%v, %v, %s, %x) error: %v", @@ -104,52 +99,3 @@ func insertChannel(initialChanID, confirmedChanId uint64, channelPoint string, n initialChanID, confirmedChanId, nodeID, c.String()) return nil } - -func lastForwardingEvent() (int64, error) { - var last int64 - err := pgxPool.QueryRow(context.Background(), - `SELECT coalesce(MAX("timestamp"), 0) AS last FROM forwarding_history`).Scan(&last) - if err != nil { - return 0, err - } - return last, nil -} - -func insertForwardingEvents(rowSrc pgx.CopyFromSource) error { - - tx, err := pgxPool.Begin(context.Background()) - if err != nil { - return fmt.Errorf("pgxPool.Begin() error: %w", err) - } - defer tx.Rollback(context.Background()) - - _, err = tx.Exec(context.Background(), ` - CREATE TEMP TABLE tmp_table ON COMMIT DROP AS - SELECT * - FROM forwarding_history - WITH NO DATA; - `) - if err != nil { - return fmt.Errorf("CREATE TEMP TABLE error: %w", err) - } - - count, err := tx.CopyFrom(context.Background(), - pgx.Identifier{"tmp_table"}, - []string{"timestamp", "chanid_in", "chanid_out", "amt_msat_in", "amt_msat_out"}, rowSrc) - if err != nil { - return fmt.Errorf("CopyFrom() error: %w", err) - } - log.Printf("count1: %v", count) - - cmdTag, err := tx.Exec(context.Background(), ` - INSERT INTO forwarding_history - SELECT * - FROM tmp_table - ON CONFLICT DO NOTHING - `) - if err != nil { - return fmt.Errorf("INSERT INTO forwarding_history error: %w", err) - } - log.Printf("count2: %v", cmdTag.RowsAffected()) - return tx.Commit(context.Background()) -} diff --git a/server.go b/server.go index f14152f..184e794 100644 --- a/server.go +++ b/server.go @@ -12,6 +12,7 @@ import ( "github.com/breez/lspd/btceclegacy" "github.com/breez/lspd/config" + "github.com/breez/lspd/interceptor" "github.com/breez/lspd/lightning" lspdrpc "github.com/breez/lspd/rpc" ecies "github.com/ecies/go/v2" @@ -37,6 +38,7 @@ type server struct { lis net.Listener s *grpc.Server nodes map[string]*node + store interceptor.InterceptStore } type node struct { @@ -114,7 +116,7 @@ func (s *server) RegisterPayment(ctx context.Context, in *lspdrpc.RegisterPaymen 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, pi.Tag) + err = s.store.RegisterPayment(pi.Destination, pi.PaymentHash, pi.PaymentSecret, pi.IncomingAmountMsat, pi.OutgoingAmountMsat, pi.Tag) if err != nil { log.Printf("RegisterPayment() error: %v", err) return nil, fmt.Errorf("RegisterPayment() error: %w", err) @@ -261,7 +263,12 @@ func (s *server) CheckChannels(ctx context.Context, in *lspdrpc.Encrypted) (*lsp return &lspdrpc.Encrypted{Data: encrypted}, nil } -func NewGrpcServer(configs []*config.NodeConfig, address string, certmagicDomain string) (*server, error) { +func NewGrpcServer( + configs []*config.NodeConfig, + address string, + certmagicDomain string, + store interceptor.InterceptStore, +) (*server, error) { if len(configs) == 0 { return nil, fmt.Errorf("no nodes supplied") } @@ -319,6 +326,7 @@ func NewGrpcServer(configs []*config.NodeConfig, address string, certmagicDomain address: address, certmagicDomain: certmagicDomain, nodes: nodes, + store: store, }, nil } @@ -409,3 +417,14 @@ func getNode(ctx context.Context) (*node, error) { return node, nil } + +func checkPayment(config *config.NodeConfig, incomingAmountMsat, outgoingAmountMsat int64) error { + fees := incomingAmountMsat * config.ChannelFeePermyriad / 10_000 / 1_000 * 1_000 + if fees < config.ChannelMinimumFeeMsat { + fees = config.ChannelMinimumFeeMsat + } + if incomingAmountMsat-outgoingAmountMsat < fees { + return fmt.Errorf("not enough fees") + } + return nil +} From c1b80420df20c15d3356acdcd3f186952b1e01df Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Fri, 24 Mar 2023 16:08:42 +0100 Subject: [PATCH 163/214] cleanup: move types to appropriate packages --- cln_client.go => cln/cln_client.go | 2 +- cln_interceptor.go => cln/cln_interceptor.go | 38 ++-- email.go => interceptor/email.go | 2 +- .../htlc_interceptor.go | 2 +- intercept.go => interceptor/intercept.go | 162 +++++++++--------- lnd_client.go => lnd/client.go | 2 +- .../forwarding_history.go | 7 +- lnd_interceptor.go => lnd/interceptor.go | 31 ++-- .../macaroon_credential.go | 2 +- main.go | 23 ++- server.go | 6 +- 11 files changed, 144 insertions(+), 133 deletions(-) rename cln_client.go => cln/cln_client.go (99%) rename cln_interceptor.go => cln/cln_interceptor.go (87%) rename email.go => interceptor/email.go (99%) rename htlc_interceptor.go => interceptor/htlc_interceptor.go (80%) rename intercept.go => interceptor/intercept.go (65%) rename lnd_client.go => lnd/client.go (99%) rename forwarding_history.go => lnd/forwarding_history.go (97%) rename lnd_interceptor.go => lnd/interceptor.go (85%) rename lnd_macaroon_credential.go => lnd/macaroon_credential.go (97%) diff --git a/cln_client.go b/cln/cln_client.go similarity index 99% rename from cln_client.go rename to cln/cln_client.go index 0026151..8475ce6 100644 --- a/cln_client.go +++ b/cln/cln_client.go @@ -1,4 +1,4 @@ -package main +package cln import ( "encoding/hex" diff --git a/cln_interceptor.go b/cln/cln_interceptor.go similarity index 87% rename from cln_interceptor.go rename to cln/cln_interceptor.go index cce2c94..71c4cc0 100644 --- a/cln_interceptor.go +++ b/cln/cln_interceptor.go @@ -1,4 +1,4 @@ -package main +package cln import ( "bytes" @@ -12,6 +12,7 @@ import ( "github.com/breez/lspd/cln_plugin/proto" "github.com/breez/lspd/config" + "github.com/breez/lspd/interceptor" sphinx "github.com/lightningnetwork/lightning-onion" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/record" @@ -24,7 +25,7 @@ import ( ) type ClnHtlcInterceptor struct { - interceptor *Interceptor + interceptor *interceptor.Interceptor config *config.NodeConfig pluginAddress string client *ClnClient @@ -36,11 +37,12 @@ type ClnHtlcInterceptor struct { cancel context.CancelFunc } -func NewClnHtlcInterceptor(conf *config.NodeConfig, client *ClnClient, interceptor *Interceptor) (*ClnHtlcInterceptor, error) { +func NewClnHtlcInterceptor(conf *config.NodeConfig, client *ClnClient, interceptor *interceptor.Interceptor) (*ClnHtlcInterceptor, error) { i := &ClnHtlcInterceptor{ config: conf, pluginAddress: conf.Cln.PluginAddress, client: client, + interceptor: interceptor, } i.initWg.Add(1) @@ -163,14 +165,14 @@ func (i *ClnHtlcInterceptor) intercept() error { i.doneWg.Done() } interceptResult := i.interceptor.Intercept(nextHop, paymentHash, request.Onion.ForwardMsat, request.Onion.OutgoingCltvValue, request.Htlc.CltvExpiry) - switch interceptResult.action { - case INTERCEPT_RESUME_WITH_ONION: + switch interceptResult.Action { + case interceptor.INTERCEPT_RESUME_WITH_ONION: interceptorClient.Send(i.resumeWithOnion(request, interceptResult)) - case INTERCEPT_FAIL_HTLC_WITH_CODE: + case interceptor.INTERCEPT_FAIL_HTLC_WITH_CODE: interceptorClient.Send( - i.failWithCode(request, interceptResult.failureCode), + i.failWithCode(request, interceptResult.FailureCode), ) - case INTERCEPT_RESUME: + case interceptor.INTERCEPT_RESUME: fallthrough default: interceptorClient.Send( @@ -202,22 +204,22 @@ func (i *ClnHtlcInterceptor) WaitStarted() { i.initWg.Wait() } -func (i *ClnHtlcInterceptor) resumeWithOnion(request *proto.HtlcAccepted, interceptResult interceptResult) *proto.HtlcResolution { +func (i *ClnHtlcInterceptor) resumeWithOnion(request *proto.HtlcAccepted, interceptResult interceptor.InterceptResult) *proto.HtlcResolution { //decoding and encoding onion with alias in type 6 record. payload, err := hex.DecodeString(request.Onion.Payload) if err != nil { log.Printf("resumeWithOnion: hex.DecodeString(%v) error: %v", request.Onion.Payload, err) - return i.failWithCode(request, FAILURE_TEMPORARY_CHANNEL_FAILURE) + return i.failWithCode(request, interceptor.FAILURE_TEMPORARY_CHANNEL_FAILURE) } - newPayload, err := encodePayloadWithNextHop(payload, interceptResult.channelId) + newPayload, err := encodePayloadWithNextHop(payload, interceptResult.ChannelId) if err != nil { log.Printf("encodePayloadWithNextHop error: %v", err) - return i.failWithCode(request, FAILURE_TEMPORARY_CHANNEL_FAILURE) + return i.failWithCode(request, interceptor.FAILURE_TEMPORARY_CHANNEL_FAILURE) } newPayloadStr := hex.EncodeToString(newPayload) - chanId := lnwire.NewChanIDFromOutPoint(interceptResult.channelPoint).String() + chanId := lnwire.NewChanIDFromOutPoint(interceptResult.ChannelPoint).String() log.Printf("forwarding htlc to the destination node and a new private channel was opened") return &proto.HtlcResolution{ Correlationid: request.Correlationid, @@ -239,7 +241,7 @@ func (i *ClnHtlcInterceptor) defaultResolution(request *proto.HtlcAccepted) *pro } } -func (i *ClnHtlcInterceptor) failWithCode(request *proto.HtlcAccepted, code interceptFailureCode) *proto.HtlcResolution { +func (i *ClnHtlcInterceptor) failWithCode(request *proto.HtlcAccepted, code interceptor.InterceptFailureCode) *proto.HtlcResolution { return &proto.HtlcResolution{ Correlationid: request.Correlationid, Outcome: &proto.HtlcResolution_Fail{ @@ -298,13 +300,13 @@ func encodePayloadWithNextHop(payload []byte, channelId uint64) ([]byte, error) return newPayloadBuf.Bytes(), nil } -func (i *ClnHtlcInterceptor) mapFailureCode(original interceptFailureCode) string { +func (i *ClnHtlcInterceptor) mapFailureCode(original interceptor.InterceptFailureCode) string { switch original { - case FAILURE_TEMPORARY_CHANNEL_FAILURE: + case interceptor.FAILURE_TEMPORARY_CHANNEL_FAILURE: return "1007" - case FAILURE_TEMPORARY_NODE_FAILURE: + case interceptor.FAILURE_TEMPORARY_NODE_FAILURE: return "2002" - case FAILURE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS: + case interceptor.FAILURE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS: return "400F" default: log.Printf("Unknown failure code %v, default to temporary channel failure.", original) diff --git a/email.go b/interceptor/email.go similarity index 99% rename from email.go rename to interceptor/email.go index b0a5fdb..857aacf 100644 --- a/email.go +++ b/interceptor/email.go @@ -1,4 +1,4 @@ -package main +package interceptor import ( "bytes" diff --git a/htlc_interceptor.go b/interceptor/htlc_interceptor.go similarity index 80% rename from htlc_interceptor.go rename to interceptor/htlc_interceptor.go index e572f0d..f824b36 100644 --- a/htlc_interceptor.go +++ b/interceptor/htlc_interceptor.go @@ -1,4 +1,4 @@ -package main +package interceptor type HtlcInterceptor interface { Start() error diff --git a/intercept.go b/interceptor/intercept.go similarity index 65% rename from intercept.go rename to interceptor/intercept.go index 1460a2b..7d8e1cb 100644 --- a/intercept.go +++ b/interceptor/intercept.go @@ -1,4 +1,4 @@ -package main +package interceptor import ( "bytes" @@ -11,7 +11,6 @@ import ( "github.com/breez/lspd/chain" "github.com/breez/lspd/config" - "github.com/breez/lspd/interceptor" "github.com/breez/lspd/lightning" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/wire" @@ -22,94 +21,97 @@ import ( "golang.org/x/sync/singleflight" ) -type interceptAction int +type InterceptAction int const ( - INTERCEPT_RESUME interceptAction = 0 - INTERCEPT_RESUME_WITH_ONION interceptAction = 1 - INTERCEPT_FAIL_HTLC_WITH_CODE interceptAction = 2 + INTERCEPT_RESUME InterceptAction = 0 + INTERCEPT_RESUME_WITH_ONION InterceptAction = 1 + INTERCEPT_FAIL_HTLC_WITH_CODE InterceptAction = 2 ) -type interceptFailureCode uint16 +type InterceptFailureCode uint16 var ( - FAILURE_TEMPORARY_CHANNEL_FAILURE interceptFailureCode = 0x1007 - FAILURE_TEMPORARY_NODE_FAILURE interceptFailureCode = 0x2002 - FAILURE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS interceptFailureCode = 0x400F + FAILURE_TEMPORARY_CHANNEL_FAILURE InterceptFailureCode = 0x1007 + FAILURE_TEMPORARY_NODE_FAILURE InterceptFailureCode = 0x2002 + FAILURE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS InterceptFailureCode = 0x400F ) -var payHashGroup singleflight.Group -var feeEstimator chain.FeeEstimator -var feeStrategy chain.FeeStrategy - -type interceptResult struct { - action interceptAction - failureCode interceptFailureCode - destination []byte - amountMsat uint64 - channelPoint *wire.OutPoint - channelId uint64 - onionBlob []byte +type InterceptResult struct { + Action InterceptAction + FailureCode InterceptFailureCode + Destination []byte + AmountMsat uint64 + ChannelPoint *wire.OutPoint + ChannelId uint64 + OnionBlob []byte } type Interceptor struct { - client lightning.Client - config *config.NodeConfig - store interceptor.InterceptStore + client lightning.Client + config *config.NodeConfig + store InterceptStore + feeEstimator chain.FeeEstimator + feeStrategy chain.FeeStrategy + payHashGroup singleflight.Group } func NewInterceptor( client lightning.Client, config *config.NodeConfig, - store interceptor.InterceptStore, + store InterceptStore, + feeEstimator chain.FeeEstimator, + feeStrategy chain.FeeStrategy, ) *Interceptor { return &Interceptor{ - client: client, - config: config, - store: store, + client: client, + config: config, + store: store, + feeEstimator: feeEstimator, + feeStrategy: feeStrategy, } } -func (i *Interceptor) Intercept(nextHop string, reqPaymentHash []byte, reqOutgoingAmountMsat uint64, reqOutgoingExpiry uint32, reqIncomingExpiry uint32) interceptResult { +func (i *Interceptor) Intercept(nextHop string, reqPaymentHash []byte, reqOutgoingAmountMsat uint64, reqOutgoingExpiry uint32, reqIncomingExpiry uint32) InterceptResult { reqPaymentHashStr := hex.EncodeToString(reqPaymentHash) - resp, _, _ := payHashGroup.Do(reqPaymentHashStr, func() (interface{}, error) { + resp, _, _ := i.payHashGroup.Do(reqPaymentHashStr, func() (interface{}, error) { paymentHash, paymentSecret, destination, incomingAmountMsat, outgoingAmountMsat, channelPoint, err := i.store.PaymentInfo(reqPaymentHash) if err != nil { log.Printf("paymentInfo(%x) error: %v", reqPaymentHash, err) - return interceptResult{ - action: INTERCEPT_FAIL_HTLC_WITH_CODE, - failureCode: FAILURE_TEMPORARY_NODE_FAILURE, + return InterceptResult{ + Action: INTERCEPT_FAIL_HTLC_WITH_CODE, + FailureCode: FAILURE_TEMPORARY_NODE_FAILURE, }, nil } log.Printf("paymentHash:%x\npaymentSecret:%x\ndestination:%x\nincomingAmountMsat:%v\noutgoingAmountMsat:%v", paymentHash, paymentSecret, destination, incomingAmountMsat, outgoingAmountMsat) if paymentSecret == nil || (nextHop != "" && nextHop != hex.EncodeToString(destination)) { - return interceptResult{ - action: INTERCEPT_RESUME, + return InterceptResult{ + Action: INTERCEPT_RESUME, }, nil } if channelPoint == nil { if bytes.Equal(paymentHash, reqPaymentHash) { if int64(reqIncomingExpiry)-int64(reqOutgoingExpiry) < int64(i.config.TimeLockDelta) { - return interceptResult{ - action: INTERCEPT_FAIL_HTLC_WITH_CODE, - failureCode: FAILURE_TEMPORARY_CHANNEL_FAILURE, + return InterceptResult{ + Action: INTERCEPT_FAIL_HTLC_WITH_CODE, + FailureCode: FAILURE_TEMPORARY_CHANNEL_FAILURE, }, nil } channelPoint, err = i.openChannel(reqPaymentHash, destination, incomingAmountMsat) if err != nil { log.Printf("openChannel(%x, %v) err: %v", destination, incomingAmountMsat, err) - return interceptResult{ - action: INTERCEPT_FAIL_HTLC_WITH_CODE, - failureCode: FAILURE_TEMPORARY_CHANNEL_FAILURE, + return InterceptResult{ + Action: INTERCEPT_FAIL_HTLC_WITH_CODE, + FailureCode: FAILURE_TEMPORARY_CHANNEL_FAILURE, }, nil } } else { //probing - return interceptResult{ - action: INTERCEPT_FAIL_HTLC_WITH_CODE, - failureCode: FAILURE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS, + return InterceptResult{ + Action: INTERCEPT_FAIL_HTLC_WITH_CODE, + FailureCode: FAILURE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS, }, nil } } @@ -117,18 +119,18 @@ func (i *Interceptor) Intercept(nextHop string, reqPaymentHash []byte, reqOutgoi pubKey, err := btcec.ParsePubKey(destination) if err != nil { log.Printf("btcec.ParsePubKey(%x): %v", destination, err) - return interceptResult{ - action: INTERCEPT_FAIL_HTLC_WITH_CODE, - failureCode: FAILURE_TEMPORARY_CHANNEL_FAILURE, + return InterceptResult{ + Action: INTERCEPT_FAIL_HTLC_WITH_CODE, + FailureCode: FAILURE_TEMPORARY_CHANNEL_FAILURE, }, nil } sessionKey, err := btcec.NewPrivateKey() if err != nil { log.Printf("btcec.NewPrivateKey(): %v", err) - return interceptResult{ - action: INTERCEPT_FAIL_HTLC_WITH_CODE, - failureCode: FAILURE_TEMPORARY_CHANNEL_FAILURE, + return InterceptResult{ + Action: INTERCEPT_FAIL_HTLC_WITH_CODE, + FailureCode: FAILURE_TEMPORARY_CHANNEL_FAILURE, }, nil } @@ -148,18 +150,18 @@ func (i *Interceptor) Intercept(nextHop string, reqPaymentHash []byte, reqOutgoi err = hop.PackHopPayload(&b, uint64(0)) if err != nil { log.Printf("hop.PackHopPayload(): %v", err) - return interceptResult{ - action: INTERCEPT_FAIL_HTLC_WITH_CODE, - failureCode: FAILURE_TEMPORARY_CHANNEL_FAILURE, + return InterceptResult{ + Action: INTERCEPT_FAIL_HTLC_WITH_CODE, + FailureCode: FAILURE_TEMPORARY_CHANNEL_FAILURE, }, nil } payload, err := sphinx.NewHopPayload(nil, b.Bytes()) if err != nil { log.Printf("sphinx.NewHopPayload(): %v", err) - return interceptResult{ - action: INTERCEPT_FAIL_HTLC_WITH_CODE, - failureCode: FAILURE_TEMPORARY_CHANNEL_FAILURE, + return InterceptResult{ + Action: INTERCEPT_FAIL_HTLC_WITH_CODE, + FailureCode: FAILURE_TEMPORARY_CHANNEL_FAILURE, }, nil } @@ -174,18 +176,18 @@ func (i *Interceptor) Intercept(nextHop string, reqPaymentHash []byte, reqOutgoi ) if err != nil { log.Printf("sphinx.NewOnionPacket(): %v", err) - return interceptResult{ - action: INTERCEPT_FAIL_HTLC_WITH_CODE, - failureCode: FAILURE_TEMPORARY_CHANNEL_FAILURE, + return InterceptResult{ + Action: INTERCEPT_FAIL_HTLC_WITH_CODE, + FailureCode: FAILURE_TEMPORARY_CHANNEL_FAILURE, }, nil } var onionBlob bytes.Buffer err = sphinxPacket.Encode(&onionBlob) if err != nil { log.Printf("sphinxPacket.Encode(): %v", err) - return interceptResult{ - action: INTERCEPT_FAIL_HTLC_WITH_CODE, - failureCode: FAILURE_TEMPORARY_CHANNEL_FAILURE, + return InterceptResult{ + Action: INTERCEPT_FAIL_HTLC_WITH_CODE, + FailureCode: FAILURE_TEMPORARY_CHANNEL_FAILURE, }, nil } @@ -206,9 +208,9 @@ func (i *Interceptor) Intercept(nextHop string, reqPaymentHash []byte, reqOutgoi if err != nil { log.Printf("insertChannel error: %v", err) - return interceptResult{ - action: INTERCEPT_FAIL_HTLC_WITH_CODE, - failureCode: FAILURE_TEMPORARY_CHANNEL_FAILURE, + return InterceptResult{ + Action: INTERCEPT_FAIL_HTLC_WITH_CODE, + FailureCode: FAILURE_TEMPORARY_CHANNEL_FAILURE, }, nil } @@ -217,13 +219,13 @@ func (i *Interceptor) Intercept(nextHop string, reqPaymentHash []byte, reqOutgoi channelID = uint64(chanResult.InitialChannelID) } - return interceptResult{ - action: INTERCEPT_RESUME_WITH_ONION, - destination: destination, - channelPoint: channelPoint, - channelId: channelID, - amountMsat: uint64(amt), - onionBlob: onionBlob.Bytes(), + return InterceptResult{ + Action: INTERCEPT_RESUME_WITH_ONION, + Destination: destination, + ChannelPoint: channelPoint, + ChannelId: channelID, + AmountMsat: uint64(amt), + OnionBlob: onionBlob.Bytes(), }, nil } @@ -236,13 +238,13 @@ func (i *Interceptor) Intercept(nextHop string, reqPaymentHash []byte, reqOutgoi } log.Printf("Error: Channel failed to opened... timed out. ") - return interceptResult{ - action: INTERCEPT_FAIL_HTLC_WITH_CODE, - failureCode: FAILURE_TEMPORARY_CHANNEL_FAILURE, + return InterceptResult{ + Action: INTERCEPT_FAIL_HTLC_WITH_CODE, + FailureCode: FAILURE_TEMPORARY_CHANNEL_FAILURE, }, nil }) - return resp.(interceptResult) + return resp.(InterceptResult) } func (i *Interceptor) openChannel(paymentHash, destination []byte, incomingAmountMsat int64) (*wire.OutPoint, error) { @@ -255,10 +257,10 @@ func (i *Interceptor) openChannel(paymentHash, destination []byte, incomingAmoun confStr := "" var feeEstimation *float64 feeStr := "" - if feeEstimator != nil { - fee, err := feeEstimator.EstimateFeeRate( + if i.feeEstimator != nil { + fee, err := i.feeEstimator.EstimateFeeRate( context.Background(), - feeStrategy, + i.feeStrategy, ) if err == nil { feeEstimation = &fee.SatPerVByte diff --git a/lnd_client.go b/lnd/client.go similarity index 99% rename from lnd_client.go rename to lnd/client.go index 1ced705..0105569 100644 --- a/lnd_client.go +++ b/lnd/client.go @@ -1,4 +1,4 @@ -package main +package lnd import ( "context" diff --git a/forwarding_history.go b/lnd/forwarding_history.go similarity index 97% rename from forwarding_history.go rename to lnd/forwarding_history.go index 1fa960d..debd93f 100644 --- a/forwarding_history.go +++ b/lnd/forwarding_history.go @@ -1,4 +1,4 @@ -package main +package lnd import ( "context" @@ -8,7 +8,6 @@ import ( "time" "github.com/breez/lspd/interceptor" - "github.com/breez/lspd/lnd" "github.com/lightningnetwork/lnd/htlcswitch/hop" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/chainrpc" @@ -41,13 +40,13 @@ func (cfe *copyFromEvents) Err() error { type ForwardingHistorySync struct { client *LndClient interceptStore interceptor.InterceptStore - forwardingStore lnd.ForwardingEventStore + forwardingStore ForwardingEventStore } func NewForwardingHistorySync( client *LndClient, interceptStore interceptor.InterceptStore, - forwardingStore lnd.ForwardingEventStore, + forwardingStore ForwardingEventStore, ) *ForwardingHistorySync { return &ForwardingHistorySync{ client: client, diff --git a/lnd_interceptor.go b/lnd/interceptor.go similarity index 85% rename from lnd_interceptor.go rename to lnd/interceptor.go index ca84cd8..7affba9 100644 --- a/lnd_interceptor.go +++ b/lnd/interceptor.go @@ -1,4 +1,4 @@ -package main +package lnd import ( "context" @@ -8,6 +8,7 @@ import ( "time" "github.com/breez/lspd/config" + "github.com/breez/lspd/interceptor" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/routerrpc" "google.golang.org/grpc/codes" @@ -16,7 +17,7 @@ import ( type LndHtlcInterceptor struct { fwsync *ForwardingHistorySync - interceptor *Interceptor + interceptor *interceptor.Interceptor config *config.NodeConfig client *LndClient stopRequested bool @@ -30,7 +31,7 @@ func NewLndHtlcInterceptor( conf *config.NodeConfig, client *LndClient, fwsync *ForwardingHistorySync, - interceptor *Interceptor, + interceptor *interceptor.Interceptor, ) (*LndHtlcInterceptor, error) { i := &LndHtlcInterceptor{ config: conf, @@ -152,22 +153,22 @@ func (i *LndHtlcInterceptor) intercept() error { i.doneWg.Add(1) go func() { interceptResult := i.interceptor.Intercept(nextHop, request.PaymentHash, request.OutgoingAmountMsat, request.OutgoingExpiry, request.IncomingExpiry) - switch interceptResult.action { - case INTERCEPT_RESUME_WITH_ONION: + switch interceptResult.Action { + case interceptor.INTERCEPT_RESUME_WITH_ONION: interceptorClient.Send(&routerrpc.ForwardHtlcInterceptResponse{ IncomingCircuitKey: request.IncomingCircuitKey, Action: routerrpc.ResolveHoldForwardAction_RESUME, - OutgoingAmountMsat: interceptResult.amountMsat, - OutgoingRequestedChanId: uint64(interceptResult.channelId), - OnionBlob: interceptResult.onionBlob, + OutgoingAmountMsat: interceptResult.AmountMsat, + OutgoingRequestedChanId: uint64(interceptResult.ChannelId), + OnionBlob: interceptResult.OnionBlob, }) - case INTERCEPT_FAIL_HTLC_WITH_CODE: + case interceptor.INTERCEPT_FAIL_HTLC_WITH_CODE: interceptorClient.Send(&routerrpc.ForwardHtlcInterceptResponse{ IncomingCircuitKey: request.IncomingCircuitKey, Action: routerrpc.ResolveHoldForwardAction_FAIL, - FailureCode: i.mapFailureCode(interceptResult.failureCode), + FailureCode: i.mapFailureCode(interceptResult.FailureCode), }) - case INTERCEPT_RESUME: + case interceptor.INTERCEPT_RESUME: fallthrough default: interceptorClient.Send(&routerrpc.ForwardHtlcInterceptResponse{ @@ -187,13 +188,13 @@ func (i *LndHtlcInterceptor) intercept() error { } } -func (i *LndHtlcInterceptor) mapFailureCode(original interceptFailureCode) lnrpc.Failure_FailureCode { +func (i *LndHtlcInterceptor) mapFailureCode(original interceptor.InterceptFailureCode) lnrpc.Failure_FailureCode { switch original { - case FAILURE_TEMPORARY_CHANNEL_FAILURE: + case interceptor.FAILURE_TEMPORARY_CHANNEL_FAILURE: return lnrpc.Failure_TEMPORARY_CHANNEL_FAILURE - case FAILURE_TEMPORARY_NODE_FAILURE: + case interceptor.FAILURE_TEMPORARY_NODE_FAILURE: return lnrpc.Failure_TEMPORARY_NODE_FAILURE - case FAILURE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS: + case interceptor.FAILURE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS: return lnrpc.Failure_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS default: log.Printf("Unknown failure code %v, default to temporary channel failure.", original) diff --git a/lnd_macaroon_credential.go b/lnd/macaroon_credential.go similarity index 97% rename from lnd_macaroon_credential.go rename to lnd/macaroon_credential.go index 8e70f87..f65ddaa 100644 --- a/lnd_macaroon_credential.go +++ b/lnd/macaroon_credential.go @@ -1,4 +1,4 @@ -package main +package lnd import ( "context" diff --git a/main.go b/main.go index 1f40262..a2821a5 100644 --- a/main.go +++ b/main.go @@ -11,7 +11,10 @@ import ( "syscall" "github.com/breez/lspd/chain" + "github.com/breez/lspd/cln" "github.com/breez/lspd/config" + "github.com/breez/lspd/interceptor" + "github.com/breez/lspd/lnd" "github.com/breez/lspd/mempool" "github.com/breez/lspd/postgresql" "github.com/btcsuite/btcd/btcec/v2" @@ -38,6 +41,8 @@ func main() { log.Fatalf("need at least one node configured in NODES.") } + var feeEstimator chain.FeeEstimator + var feeStrategy chain.FeeStrategy useMempool := os.Getenv("USE_MEMPOOL_FEE_ESTIMATION") == "true" if useMempool { mempoolUrl := os.Getenv("MEMPOOL_API_BASE_URL") @@ -73,31 +78,31 @@ func main() { interceptStore := postgresql.NewPostgresInterceptStore(pool) forwardingStore := postgresql.NewForwardingEventStore(pool) - var interceptors []HtlcInterceptor + var interceptors []interceptor.HtlcInterceptor for _, node := range nodes { - var htlcInterceptor HtlcInterceptor + var htlcInterceptor interceptor.HtlcInterceptor if node.Lnd != nil { - client, err := NewLndClient(node.Lnd) + client, err := lnd.NewLndClient(node.Lnd) if err != nil { log.Fatalf("failed to initialize LND client: %v", err) } - fwsync := NewForwardingHistorySync(client, interceptStore, forwardingStore) - interceptor := NewInterceptor(client, node, interceptStore) - htlcInterceptor, err = NewLndHtlcInterceptor(node, client, fwsync, interceptor) + fwsync := lnd.NewForwardingHistorySync(client, interceptStore, forwardingStore) + interceptor := interceptor.NewInterceptor(client, node, interceptStore, feeEstimator, feeStrategy) + htlcInterceptor, err = lnd.NewLndHtlcInterceptor(node, client, fwsync, interceptor) if err != nil { log.Fatalf("failed to initialize LND interceptor: %v", err) } } if node.Cln != nil { - client, err := NewClnClient(node.Cln.SocketPath) + client, err := cln.NewClnClient(node.Cln.SocketPath) if err != nil { log.Fatalf("failed to initialize CLN client: %v", err) } - interceptor := NewInterceptor(client, node, interceptStore) - htlcInterceptor, err = NewClnHtlcInterceptor(node, client, interceptor) + interceptor := interceptor.NewInterceptor(client, node, interceptStore, feeEstimator, feeStrategy) + htlcInterceptor, err = cln.NewClnHtlcInterceptor(node, client, interceptor) if err != nil { log.Fatalf("failed to initialize CLN interceptor: %v", err) } diff --git a/server.go b/server.go index 184e794..ac87928 100644 --- a/server.go +++ b/server.go @@ -11,9 +11,11 @@ import ( "strings" "github.com/breez/lspd/btceclegacy" + "github.com/breez/lspd/cln" "github.com/breez/lspd/config" "github.com/breez/lspd/interceptor" "github.com/breez/lspd/lightning" + "github.com/breez/lspd/lnd" lspdrpc "github.com/breez/lspd/rpc" ecies "github.com/ecies/go/v2" "github.com/golang/protobuf/proto" @@ -301,14 +303,14 @@ func NewGrpcServer( } if config.Lnd != nil { - node.client, err = NewLndClient(config.Lnd) + node.client, err = lnd.NewLndClient(config.Lnd) if err != nil { return nil, err } } if config.Cln != nil { - node.client, err = NewClnClient(config.Cln.SocketPath) + node.client, err = cln.NewClnClient(config.Cln.SocketPath) if err != nil { return nil, err } From eea9ec3c47e5d1f02828479220e0acafae8232a1 Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Fri, 24 Mar 2023 23:44:10 +0100 Subject: [PATCH 164/214] cleanup: wrong server object was used --- server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server.go b/server.go index ac87928..ed7e528 100644 --- a/server.go +++ b/server.go @@ -388,7 +388,7 @@ func (s *server) Start() error { return nil, status.Errorf(codes.PermissionDenied, "Not authorized") }), ) - lspdrpc.RegisterChannelOpenerServer(srv, &server{}) + lspdrpc.RegisterChannelOpenerServer(srv, s) s.s = srv s.lis = lis From 8d745f4344c8726bb67f93cfe134d80e64a13881 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 13 Apr 2023 08:45:35 +0000 Subject: [PATCH 165/214] Bump github.com/docker/docker Bumps [github.com/docker/docker](https://github.com/docker/docker) from 20.10.21+incompatible to 20.10.24+incompatible. - [Release notes](https://github.com/docker/docker/releases) - [Commits](https://github.com/docker/docker/compare/v20.10.21...v20.10.24) --- updated-dependencies: - dependency-name: github.com/docker/docker dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 4085f37..115e08b 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 github.com/caddyserver/certmagic v0.11.2 github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 - github.com/docker/docker v20.10.21+incompatible + github.com/docker/docker v20.10.24+incompatible github.com/docker/go-connections v0.4.0 github.com/golang/protobuf v1.5.2 github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 From 415a46a6fe597032904b0ece7a179f9b7db1d2c1 Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Fri, 20 Jan 2023 16:24:06 +0100 Subject: [PATCH 166/214] cln: use amt_to_forward in payload to charge fees --- cln/cln_interceptor.go | 21 +++- itest/bob_offline_test.go | 5 +- itest/breez_client.go | 1 + itest/cln_breez_client.go | 202 ++++++++++++++++++++++++++++-- itest/cln_lspd_node.go | 3 - itest/cltv_test.go | 4 +- itest/intercept_zero_conf_test.go | 10 +- itest/lnd_breez_client.go | 4 + itest/lnd_lspd_node.go | 4 - itest/lspd_node.go | 1 - itest/no_balance_test.go | 5 +- itest/probing_test.go | 5 +- itest/test_common.go | 11 +- itest/zero_conf_utxo_test.go | 5 +- itest/zero_reserve_test.go | 5 +- 15 files changed, 236 insertions(+), 50 deletions(-) diff --git a/cln/cln_interceptor.go b/cln/cln_interceptor.go index 71c4cc0..0efa6a8 100644 --- a/cln/cln_interceptor.go +++ b/cln/cln_interceptor.go @@ -211,7 +211,7 @@ func (i *ClnHtlcInterceptor) resumeWithOnion(request *proto.HtlcAccepted, interc log.Printf("resumeWithOnion: hex.DecodeString(%v) error: %v", request.Onion.Payload, err) return i.failWithCode(request, interceptor.FAILURE_TEMPORARY_CHANNEL_FAILURE) } - newPayload, err := encodePayloadWithNextHop(payload, interceptResult.ChannelId) + newPayload, err := encodePayloadWithNextHop(payload, interceptResult.ChannelId, interceptResult.AmountMsat) if err != nil { log.Printf("encodePayloadWithNextHop error: %v", err) return i.failWithCode(request, interceptor.FAILURE_TEMPORARY_CHANNEL_FAILURE) @@ -254,7 +254,7 @@ func (i *ClnHtlcInterceptor) failWithCode(request *proto.HtlcAccepted, code inte } } -func encodePayloadWithNextHop(payload []byte, channelId uint64) ([]byte, error) { +func encodePayloadWithNextHop(payload []byte, channelId uint64, amountToForward uint64) ([]byte, error) { bufReader := bytes.NewBuffer(payload) var b [8]byte varInt, err := sphinx.ReadVarInt(bufReader, &b) @@ -274,15 +274,26 @@ func encodePayloadWithNextHop(payload []byte, channelId uint64) ([]byte, error) } tt := record.NewNextHopIDRecord(&channelId) - buf := bytes.NewBuffer([]byte{}) - if err := tt.Encode(buf); err != nil { + ttbuf := bytes.NewBuffer([]byte{}) + if err := tt.Encode(ttbuf); err != nil { return nil, fmt.Errorf("failed to encode nexthop %x: %v", innerPayload[:], err) } + amt := record.NewAmtToFwdRecord(&amountToForward) + amtbuf := bytes.NewBuffer([]byte{}) + if err := amt.Encode(amtbuf); err != nil { + return nil, fmt.Errorf("failed to encode AmtToFwd %x: %v", innerPayload[:], err) + } + uTlvMap := make(map[uint64][]byte) for t, b := range tlvMap { if t == record.NextHopOnionType { - uTlvMap[uint64(t)] = buf.Bytes() + uTlvMap[uint64(t)] = ttbuf.Bytes() + continue + } + + if t == record.AmtOnionType { + uTlvMap[uint64(t)] = amtbuf.Bytes() continue } uTlvMap[uint64(t)] = b diff --git a/itest/bob_offline_test.go b/itest/bob_offline_test.go index bd7d505..45551cb 100644 --- a/itest/bob_offline_test.go +++ b/itest/bob_offline_test.go @@ -23,7 +23,7 @@ func testFailureBobOffline(p *testParams) { log.Printf("Adding bob's invoices") outerAmountMsat := uint64(2100000) - innerAmountMsat, lspAmountMsat := calculateInnerAmountMsat(p.lsp, outerAmountMsat) + innerAmountMsat := calculateInnerAmountMsat(p.lsp, outerAmountMsat) description := "Please pay me" innerInvoice, outerInvoice := GenerateInvoices(p.BreezClient(), generateInvoicesRequest{ @@ -33,6 +33,7 @@ func testFailureBobOffline(p *testParams) { lsp: p.lsp, }) + p.BreezClient().SetHtlcAcceptor(innerAmountMsat) log.Print("Connecting bob to lspd") p.BreezClient().Node().ConnectPeer(p.lsp.LightningNode()) @@ -42,7 +43,7 @@ func testFailureBobOffline(p *testParams) { PaymentSecret: innerInvoice.paymentSecret, Destination: p.BreezClient().Node().NodeId(), IncomingAmountMsat: int64(outerAmountMsat), - OutgoingAmountMsat: int64(lspAmountMsat), + OutgoingAmountMsat: int64(innerAmountMsat), }) // Kill the mobile client diff --git a/itest/breez_client.go b/itest/breez_client.go index a510959..7b96ca2 100644 --- a/itest/breez_client.go +++ b/itest/breez_client.go @@ -18,6 +18,7 @@ type BreezClient interface { Node() lntest.LightningNode Start() Stop() error + SetHtlcAcceptor(totalMsat uint64) } type generateInvoicesRequest struct { diff --git a/itest/cln_breez_client.go b/itest/cln_breez_client.go index ebd3afe..7cf1911 100644 --- a/itest/cln_breez_client.go +++ b/itest/cln_breez_client.go @@ -2,12 +2,26 @@ package itest import ( "bufio" + "bytes" + "context" + "encoding/hex" "fmt" + "io" + "log" "os" "path/filepath" "sync" + "time" "github.com/breez/lntest" + "github.com/breez/lspd/cln_plugin/proto" + sphinx "github.com/lightningnetwork/lightning-onion" + "github.com/lightningnetwork/lnd/lnwire" + "github.com/lightningnetwork/lnd/record" + "github.com/lightningnetwork/lnd/tlv" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" + "google.golang.org/grpc/keepalive" ) var pluginContent string = `#!/usr/bin/env python3 @@ -37,23 +51,34 @@ python %s ` type clnBreezClient struct { - name string - scriptDir string - pluginFilePath string - harness *lntest.TestHarness - isInitialized bool - node *lntest.ClnNode - mtx sync.Mutex + name string + scriptDir string + pluginFilePath string + htlcAcceptorAddress string + htlcAcceptor func(*proto.HtlcAccepted) *proto.HtlcResolution + htlcAcceptorCancel context.CancelFunc + harness *lntest.TestHarness + isInitialized bool + node *lntest.ClnNode + mtx sync.Mutex } func newClnBreezClient(h *lntest.TestHarness, m *lntest.Miner, name string) BreezClient { scriptDir := h.GetDirectory(name) pluginFilePath := filepath.Join(scriptDir, "start_zero_conf_plugin.sh") + htlcAcceptorPort, err := lntest.GetPort() + if err != nil { + h.T.Fatalf("Could not get port for htlc acceptor plugin: %v", err) + } + + htlcAcceptorAddress := fmt.Sprintf("127.0.0.1:%v", htlcAcceptorPort) node := lntest.NewClnNode( h, m, name, fmt.Sprintf("--plugin=%s", pluginFilePath), + fmt.Sprintf("--plugin=%s", *clnPluginExec), + fmt.Sprintf("--lsp.listen=%s", htlcAcceptorAddress), // NOTE: max-concurrent-htlcs is 30 on mainnet by default. In cln V22.11 // there is a check for 'all dust' commitment transactions. The max // concurrent HTLCs of both sides of the channel * dust limit must be @@ -64,11 +89,12 @@ func newClnBreezClient(h *lntest.TestHarness, m *lntest.Miner, name string) Bree ) return &clnBreezClient{ - name: name, - harness: h, - node: node, - scriptDir: scriptDir, - pluginFilePath: pluginFilePath, + name: name, + harness: h, + node: node, + scriptDir: scriptDir, + pluginFilePath: pluginFilePath, + htlcAcceptorAddress: htlcAcceptorAddress, } } @@ -94,6 +120,151 @@ func (c *clnBreezClient) Start() { } c.node.Start() + c.startHtlcAcceptor() +} + +func (c *clnBreezClient) SetHtlcAcceptor(totalMsat uint64) { + c.htlcAcceptor = func(htlc *proto.HtlcAccepted) *proto.HtlcResolution { + origPayload, err := hex.DecodeString(htlc.Onion.Payload) + if err != nil { + c.harness.T.Fatalf("failed to hex decode onion payload %s: %v", htlc.Onion.Payload, err) + } + bufReader := bytes.NewBuffer(origPayload) + var b [8]byte + varInt, err := sphinx.ReadVarInt(bufReader, &b) + if err != nil { + c.harness.T.Fatalf("Failed to read payload: %v", err) + } + + innerPayload := make([]byte, varInt) + if _, err := io.ReadFull(bufReader, innerPayload[:]); err != nil { + c.harness.T.Fatalf("failed to decode payload %x: %v", innerPayload[:], err) + } + + s, _ := tlv.NewStream() + tlvMap, err := s.DecodeWithParsedTypes(bytes.NewReader(innerPayload)) + if err != nil { + c.harness.T.Fatalf("DecodeWithParsedTypes failed for %x: %v", innerPayload[:], err) + } + + amt := record.NewAmtToFwdRecord(&htlc.Htlc.AmountMsat) + amtbuf := bytes.NewBuffer([]byte{}) + if err := amt.Encode(amtbuf); err != nil { + c.harness.T.Fatalf("failed to encode AmtToFwd %x: %v", innerPayload[:], err) + } + + uTlvMap := make(map[uint64][]byte) + for t, b := range tlvMap { + if t == record.AmtOnionType { + uTlvMap[uint64(t)] = amtbuf.Bytes() + continue + } + + if t == record.MPPOnionType { + addr := [32]byte{} + copy(addr[:], b[:32]) + mppbuf := bytes.NewBuffer([]byte{}) + mpp := record.NewMPP( + lnwire.MilliSatoshi(totalMsat), + addr, + ) + record := mpp.Record() + record.Encode(mppbuf) + uTlvMap[uint64(t)] = mppbuf.Bytes() + continue + } + + uTlvMap[uint64(t)] = b + } + tlvRecords := tlv.MapToRecords(uTlvMap) + s, err = tlv.NewStream(tlvRecords...) + if err != nil { + c.harness.T.Fatalf("tlv.NewStream(%+v) error: %v", tlvRecords, err) + } + var newPayloadBuf bytes.Buffer + err = s.Encode(&newPayloadBuf) + if err != nil { + c.harness.T.Fatalf("encode error: %v", err) + } + payload := hex.EncodeToString(newPayloadBuf.Bytes()) + + return &proto.HtlcResolution{ + Correlationid: htlc.Correlationid, + Outcome: &proto.HtlcResolution_Continue{ + Continue: &proto.HtlcContinue{ + Payload: &payload, + }, + }, + } + } +} + +func (c *clnBreezClient) startHtlcAcceptor() { + ctx, cancel := context.WithCancel(c.harness.Ctx) + c.htlcAcceptorCancel = cancel + + go func() { + for { + if ctx.Err() != nil { + return + } + + select { + case <-ctx.Done(): + return + case <-time.After(time.Second): + } + + conn, err := grpc.DialContext( + ctx, + c.htlcAcceptorAddress, + grpc.WithTransportCredentials(insecure.NewCredentials()), + grpc.WithKeepaliveParams(keepalive.ClientParameters{ + Time: time.Duration(10) * time.Second, + Timeout: time.Duration(10) * time.Second, + }), + ) + if err != nil { + log.Printf("%s: Dial htlc acceptor error: %v", c.name, err) + continue + } + + client := proto.NewClnPluginClient(conn) + for { + acceptor, err := client.HtlcStream(ctx) + if err != nil { + log.Printf("%s: client.HtlcStream() error: %v", c.name, err) + break + } + + htlc, err := acceptor.Recv() + if err != nil { + log.Printf("%s: acceptor.Recv() error: %v", c.name, err) + break + } + + f := c.htlcAcceptor + var resp *proto.HtlcResolution + if f != nil { + resp = f(htlc) + } + if resp == nil { + resp = &proto.HtlcResolution{ + Correlationid: htlc.Correlationid, + Outcome: &proto.HtlcResolution_Continue{ + Continue: &proto.HtlcContinue{}, + }, + } + } + + err = acceptor.Send(resp) + if err != nil { + log.Printf("%s: acceptor.Send() error: %v", c.name, err) + break + } + } + } + }() } func (c *clnBreezClient) initialize() error { @@ -146,10 +317,17 @@ func (c *clnBreezClient) initialize() error { lntest.PerformCleanup(cleanups) return fmt.Errorf("failed to flush plugin file '%s': %v", c.pluginFilePath, err) } + lntest.PerformCleanup(cleanups) return nil } func (c *clnBreezClient) Stop() error { + c.mtx.Lock() + defer c.mtx.Unlock() + if c.htlcAcceptorCancel != nil { + c.htlcAcceptorCancel() + c.htlcAcceptorCancel = nil + } return c.node.Stop() } diff --git a/itest/cln_lspd_node.go b/itest/cln_lspd_node.go index be3d67a..1697e26 100644 --- a/itest/cln_lspd_node.go +++ b/itest/cln_lspd_node.go @@ -214,9 +214,6 @@ func (l *ClnLspNode) LightningNode() lntest.LightningNode { return l.lightningNode } -func (l *ClnLspNode) SupportsChargingFees() bool { - return false -} func (l *ClnLspNode) PostgresBackend() *PostgresContainer { return l.lspBase.postgresBackend } diff --git a/itest/cltv_test.go b/itest/cltv_test.go index 7bc9eaf..75a9f47 100644 --- a/itest/cltv_test.go +++ b/itest/cltv_test.go @@ -23,7 +23,7 @@ func testInvalidCltv(p *testParams) { log.Printf("Adding bob's invoices") outerAmountMsat := uint64(2100000) - innerAmountMsat, lspAmountMsat := calculateInnerAmountMsat(p.lsp, outerAmountMsat) + innerAmountMsat := calculateInnerAmountMsat(p.lsp, outerAmountMsat) description := "Please pay me" innerInvoice, outerInvoice := GenerateInvoices(p.BreezClient(), generateInvoicesRequest{ @@ -42,7 +42,7 @@ func testInvalidCltv(p *testParams) { PaymentSecret: innerInvoice.paymentSecret, Destination: p.BreezClient().Node().NodeId(), IncomingAmountMsat: int64(outerAmountMsat), - OutgoingAmountMsat: int64(lspAmountMsat), + OutgoingAmountMsat: int64(innerAmountMsat), }) // TODO: Fix race waiting for htlc interceptor. diff --git a/itest/intercept_zero_conf_test.go b/itest/intercept_zero_conf_test.go index 0aec482..483840f 100644 --- a/itest/intercept_zero_conf_test.go +++ b/itest/intercept_zero_conf_test.go @@ -25,7 +25,7 @@ func testOpenZeroConfChannelOnReceive(p *testParams) { log.Printf("Adding bob's invoices") outerAmountMsat := uint64(2100000) - innerAmountMsat, lspAmountMsat := calculateInnerAmountMsat(p.lsp, outerAmountMsat) + innerAmountMsat := calculateInnerAmountMsat(p.lsp, outerAmountMsat) description := "Please pay me" innerInvoice, outerInvoice := GenerateInvoices(p.BreezClient(), generateInvoicesRequest{ @@ -34,6 +34,7 @@ func testOpenZeroConfChannelOnReceive(p *testParams) { description: description, lsp: p.lsp, }) + p.BreezClient().SetHtlcAcceptor(innerAmountMsat) log.Print("Connecting bob to lspd") p.BreezClient().Node().ConnectPeer(p.lsp.LightningNode()) @@ -44,7 +45,7 @@ func testOpenZeroConfChannelOnReceive(p *testParams) { PaymentSecret: innerInvoice.paymentSecret, Destination: p.BreezClient().Node().NodeId(), IncomingAmountMsat: int64(outerAmountMsat), - OutgoingAmountMsat: int64(lspAmountMsat), + OutgoingAmountMsat: int64(innerAmountMsat), }) // TODO: Fix race waiting for htlc interceptor. @@ -78,7 +79,7 @@ func testOpenZeroConfSingleHtlc(p *testParams) { log.Printf("Adding bob's invoices") outerAmountMsat := uint64(2100000) - innerAmountMsat, lspAmountMsat := calculateInnerAmountMsat(p.lsp, outerAmountMsat) + innerAmountMsat := calculateInnerAmountMsat(p.lsp, outerAmountMsat) description := "Please pay me" innerInvoice, outerInvoice := GenerateInvoices(p.BreezClient(), generateInvoicesRequest{ @@ -88,6 +89,7 @@ func testOpenZeroConfSingleHtlc(p *testParams) { lsp: p.lsp, }) + p.BreezClient().SetHtlcAcceptor(innerAmountMsat) log.Print("Connecting bob to lspd") p.BreezClient().Node().ConnectPeer(p.lsp.LightningNode()) @@ -97,7 +99,7 @@ func testOpenZeroConfSingleHtlc(p *testParams) { PaymentSecret: innerInvoice.paymentSecret, Destination: p.BreezClient().Node().NodeId(), IncomingAmountMsat: int64(outerAmountMsat), - OutgoingAmountMsat: int64(lspAmountMsat), + OutgoingAmountMsat: int64(innerAmountMsat), }) // TODO: Fix race waiting for htlc interceptor. diff --git a/itest/lnd_breez_client.go b/itest/lnd_breez_client.go index 6da835a..66fe7fb 100644 --- a/itest/lnd_breez_client.go +++ b/itest/lnd_breez_client.go @@ -78,6 +78,10 @@ func (c *lndBreezClient) Stop() error { return c.node.Stop() } +func (c *lndBreezClient) SetHtlcAcceptor(totalMsat uint64) { + // No need for a htlc acceptor in the LND breez client +} + func (c *lndBreezClient) startChannelAcceptor(ctx context.Context) error { client, err := c.node.LightningClient().ChannelAcceptor(ctx) if err != nil { diff --git a/itest/lnd_lspd_node.go b/itest/lnd_lspd_node.go index e8375df..9f7e0ce 100644 --- a/itest/lnd_lspd_node.go +++ b/itest/lnd_lspd_node.go @@ -230,10 +230,6 @@ func (c *LndLspNode) Rpc() lspd.ChannelOpenerClient { return c.runtime.rpc } -func (l *LndLspNode) SupportsChargingFees() bool { - return true -} - func (l *LndLspNode) NodeId() []byte { return l.lightningNode.NodeId() } diff --git a/itest/lspd_node.go b/itest/lspd_node.go index b8c6186..bfeaf10 100644 --- a/itest/lspd_node.go +++ b/itest/lspd_node.go @@ -45,7 +45,6 @@ type LspNode interface { Rpc() lspd.ChannelOpenerClient NodeId() []byte LightningNode() lntest.LightningNode - SupportsChargingFees() bool PostgresBackend() *PostgresContainer } diff --git a/itest/no_balance_test.go b/itest/no_balance_test.go index 251f753..1c93c9d 100644 --- a/itest/no_balance_test.go +++ b/itest/no_balance_test.go @@ -22,7 +22,7 @@ func testNoBalance(p *testParams) { log.Printf("Adding bob's invoices") outerAmountMsat := uint64(2100000) - innerAmountMsat, lspAmountMsat := calculateInnerAmountMsat(p.lsp, outerAmountMsat) + innerAmountMsat := calculateInnerAmountMsat(p.lsp, outerAmountMsat) description := "Please pay me" innerInvoice, outerInvoice := GenerateInvoices(p.BreezClient(), generateInvoicesRequest{ @@ -31,6 +31,7 @@ func testNoBalance(p *testParams) { description: description, lsp: p.lsp, }) + p.BreezClient().SetHtlcAcceptor(innerAmountMsat) log.Print("Connecting bob to lspd") p.BreezClient().Node().ConnectPeer(p.lsp.LightningNode()) @@ -41,7 +42,7 @@ func testNoBalance(p *testParams) { PaymentSecret: innerInvoice.paymentSecret, Destination: p.BreezClient().Node().NodeId(), IncomingAmountMsat: int64(outerAmountMsat), - OutgoingAmountMsat: int64(lspAmountMsat), + OutgoingAmountMsat: int64(innerAmountMsat), }) // TODO: Fix race waiting for htlc interceptor. diff --git a/itest/probing_test.go b/itest/probing_test.go index 2fddbcd..15a44b7 100644 --- a/itest/probing_test.go +++ b/itest/probing_test.go @@ -24,7 +24,7 @@ func testProbing(p *testParams) { log.Printf("Adding bob's invoices") outerAmountMsat := uint64(2100000) - innerAmountMsat, lspAmountMsat := calculateInnerAmountMsat(p.lsp, outerAmountMsat) + innerAmountMsat := calculateInnerAmountMsat(p.lsp, outerAmountMsat) description := "Please pay me" innerInvoice, outerInvoice := GenerateInvoices(p.BreezClient(), generateInvoicesRequest{ @@ -33,6 +33,7 @@ func testProbing(p *testParams) { description: description, lsp: p.lsp, }) + p.BreezClient().SetHtlcAcceptor(innerAmountMsat) log.Print("Connecting bob to lspd") p.BreezClient().Node().ConnectPeer(p.lsp.LightningNode()) @@ -43,7 +44,7 @@ func testProbing(p *testParams) { PaymentSecret: innerInvoice.paymentSecret, Destination: p.BreezClient().Node().NodeId(), IncomingAmountMsat: int64(outerAmountMsat), - OutgoingAmountMsat: int64(lspAmountMsat), + OutgoingAmountMsat: int64(innerAmountMsat), }) // TODO: Fix race waiting for htlc interceptor. diff --git a/itest/test_common.go b/itest/test_common.go index 5f388df..9d8f666 100644 --- a/itest/test_common.go +++ b/itest/test_common.go @@ -26,7 +26,7 @@ func AssertChannelCapacity( assert.Equal(t, ((outerAmountMsat/1000)+100000)*1000, capacityMsat) } -func calculateInnerAmountMsat(lsp LspNode, outerAmountMsat uint64) (uint64, uint64) { +func calculateInnerAmountMsat(lsp LspNode, outerAmountMsat uint64) uint64 { fee := outerAmountMsat * 40 / 10_000 / 1_000 * 1_000 if fee < 2000000 { fee = 2000000 @@ -34,14 +34,7 @@ func calculateInnerAmountMsat(lsp LspNode, outerAmountMsat uint64) (uint64, uint inner := outerAmountMsat - fee - // NOTE: If the LSP does not support charging fees (the CLN version doesn't) - // We have to pretend in the registerpayment call that the LSP WILL charge - // fees. If we update the CLN lsp to charge fees, this check can be removed. - if lsp.SupportsChargingFees() { - return inner, inner - } else { - return outerAmountMsat, inner - } + return inner } var publicChanAmount uint64 = 1000183 diff --git a/itest/zero_conf_utxo_test.go b/itest/zero_conf_utxo_test.go index d366a65..ea991fc 100644 --- a/itest/zero_conf_utxo_test.go +++ b/itest/zero_conf_utxo_test.go @@ -36,7 +36,7 @@ func testOpenZeroConfUtxo(p *testParams) { log.Printf("Adding bob's invoices") outerAmountMsat := uint64(2100000) - innerAmountMsat, lspAmountMsat := calculateInnerAmountMsat(lsp, outerAmountMsat) + innerAmountMsat := calculateInnerAmountMsat(lsp, outerAmountMsat) description := "Please pay me" innerInvoice, outerInvoice := GenerateInvoices(p.BreezClient(), generateInvoicesRequest{ @@ -45,6 +45,7 @@ func testOpenZeroConfUtxo(p *testParams) { description: description, lsp: lsp, }) + p.BreezClient().SetHtlcAcceptor(innerAmountMsat) log.Print("Connecting bob to lspd") p.BreezClient().Node().ConnectPeer(lsp.LightningNode()) @@ -55,7 +56,7 @@ func testOpenZeroConfUtxo(p *testParams) { PaymentSecret: innerInvoice.paymentSecret, Destination: p.BreezClient().Node().NodeId(), IncomingAmountMsat: int64(outerAmountMsat), - OutgoingAmountMsat: int64(lspAmountMsat), + OutgoingAmountMsat: int64(innerAmountMsat), }) // TODO: Fix race waiting for htlc interceptor. diff --git a/itest/zero_reserve_test.go b/itest/zero_reserve_test.go index 81ffdee..6b1eab3 100644 --- a/itest/zero_reserve_test.go +++ b/itest/zero_reserve_test.go @@ -23,7 +23,7 @@ func testZeroReserve(p *testParams) { log.Printf("Adding bob's invoices") outerAmountMsat := uint64(2100000) - innerAmountMsat, lspAmountMsat := calculateInnerAmountMsat(p.lsp, outerAmountMsat) + innerAmountMsat := calculateInnerAmountMsat(p.lsp, outerAmountMsat) description := "Please pay me" innerInvoice, outerInvoice := GenerateInvoices(p.BreezClient(), generateInvoicesRequest{ @@ -32,6 +32,7 @@ func testZeroReserve(p *testParams) { description: description, lsp: p.lsp, }) + p.BreezClient().SetHtlcAcceptor(innerAmountMsat) log.Print("Connecting bob to lspd") p.BreezClient().Node().ConnectPeer(p.lsp.LightningNode()) @@ -42,7 +43,7 @@ func testZeroReserve(p *testParams) { PaymentSecret: innerInvoice.paymentSecret, Destination: p.BreezClient().Node().NodeId(), IncomingAmountMsat: int64(outerAmountMsat), - OutgoingAmountMsat: int64(lspAmountMsat), + OutgoingAmountMsat: int64(innerAmountMsat), }) // TODO: Fix race waiting for htlc interceptor. From 3a24f7f827ad08672d4003cf474c3eb2a5af7e44 Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Thu, 11 May 2023 12:52:29 +0200 Subject: [PATCH 167/214] add a cached chainfee estimator --- chain/cached_fee_estimator.go | 61 +++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 chain/cached_fee_estimator.go diff --git a/chain/cached_fee_estimator.go b/chain/cached_fee_estimator.go new file mode 100644 index 0000000..4f249c6 --- /dev/null +++ b/chain/cached_fee_estimator.go @@ -0,0 +1,61 @@ +package chain + +import ( + "context" + "sync" + "time" +) + +var cacheDuration time.Duration = time.Minute * 5 + +type feeCache struct { + time time.Time + estimation *FeeEstimation +} + +type CachedFeeEstimator struct { + cache map[FeeStrategy]*feeCache + inner FeeEstimator + mtx sync.Mutex +} + +func NewCachedFeeEstimator(inner FeeEstimator) *CachedFeeEstimator { + return &CachedFeeEstimator{ + inner: inner, + cache: make(map[FeeStrategy]*feeCache), + } +} + +func (e *CachedFeeEstimator) EstimateFeeRate( + ctx context.Context, + strategy FeeStrategy, +) (*FeeEstimation, error) { + + // Make sure we're in a lock, because we're reading/writing a map. + e.mtx.Lock() + defer e.mtx.Unlock() + + // See if there's a cached value first. + cached, ok := e.cache[strategy] + + // If there is and it's still valid, return that. + if ok && cached.time.Add(cacheDuration).After(time.Now()) { + return cached.estimation, nil + } + + // There was no valid cache. + // Fetch the new fee estimate. + now := time.Now() + estimation, err := e.inner.EstimateFeeRate(ctx, strategy) + if err != nil { + return nil, err + } + + // Cache it. + e.cache[strategy] = &feeCache{ + time: now, + estimation: estimation, + } + + return estimation, nil +} From 27f7f6b3c1f90e862e6816a1c74caf5101268b5f Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Thu, 11 May 2023 12:53:05 +0200 Subject: [PATCH 168/214] add opening_fee_params to the proto --- rpc/lspd.pb.go | 533 +++++++++++++++++++++++++++++++------------------ rpc/lspd.proto | 20 +- 2 files changed, 359 insertions(+), 194 deletions(-) diff --git a/rpc/lspd.pb.go b/rpc/lspd.pb.go index aa694e9..e2a0b94 100644 --- a/rpc/lspd.pb.go +++ b/rpc/lspd.pb.go @@ -94,12 +94,17 @@ type ChannelInformationReply struct { TimeLockDelta uint32 `protobuf:"varint,8,opt,name=time_lock_delta,proto3" json:"time_lock_delta,omitempty"` /// The minimum value in millisatoshi we will require for incoming HTLCs on /// the channel. - MinHtlcMsat int64 `protobuf:"varint,9,opt,name=min_htlc_msat,proto3" json:"min_htlc_msat,omitempty"` + MinHtlcMsat int64 `protobuf:"varint,9,opt,name=min_htlc_msat,proto3" json:"min_htlc_msat,omitempty"` + // Deprecated: Do not use. ChannelFeePermyriad int64 `protobuf:"varint,10,opt,name=channel_fee_permyriad,json=channelFeePermyriad,proto3" json:"channel_fee_permyriad,omitempty"` LspPubkey []byte `protobuf:"bytes,11,opt,name=lsp_pubkey,json=lspPubkey,proto3" json:"lsp_pubkey,omitempty"` // The channel can be closed if not used this duration in seconds. - MaxInactiveDuration int64 `protobuf:"varint,12,opt,name=max_inactive_duration,json=maxInactiveDuration,proto3" json:"max_inactive_duration,omitempty"` - ChannelMinimumFeeMsat int64 `protobuf:"varint,13,opt,name=channel_minimum_fee_msat,json=channelMinimumFeeMsat,proto3" json:"channel_minimum_fee_msat,omitempty"` + // + // Deprecated: Do not use. + MaxInactiveDuration int64 `protobuf:"varint,12,opt,name=max_inactive_duration,json=maxInactiveDuration,proto3" json:"max_inactive_duration,omitempty"` + // Deprecated: Do not use. + ChannelMinimumFeeMsat int64 `protobuf:"varint,13,opt,name=channel_minimum_fee_msat,json=channelMinimumFeeMsat,proto3" json:"channel_minimum_fee_msat,omitempty"` + OpeningFeeParamsMenu []*OpeningFeeParams `protobuf:"bytes,14,rep,name=opening_fee_params_menu,json=openingFeeParamsMenu,proto3" json:"opening_fee_params_menu,omitempty"` } func (x *ChannelInformationReply) Reset() { @@ -197,6 +202,7 @@ func (x *ChannelInformationReply) GetMinHtlcMsat() int64 { return 0 } +// Deprecated: Do not use. func (x *ChannelInformationReply) GetChannelFeePermyriad() int64 { if x != nil { return x.ChannelFeePermyriad @@ -211,6 +217,7 @@ func (x *ChannelInformationReply) GetLspPubkey() []byte { return nil } +// Deprecated: Do not use. func (x *ChannelInformationReply) GetMaxInactiveDuration() int64 { if x != nil { return x.MaxInactiveDuration @@ -218,6 +225,7 @@ func (x *ChannelInformationReply) GetMaxInactiveDuration() int64 { return 0 } +// Deprecated: Do not use. func (x *ChannelInformationReply) GetChannelMinimumFeeMsat() int64 { if x != nil { return x.ChannelMinimumFeeMsat @@ -225,6 +233,101 @@ func (x *ChannelInformationReply) GetChannelMinimumFeeMsat() int64 { return 0 } +func (x *ChannelInformationReply) GetOpeningFeeParamsMenu() []*OpeningFeeParams { + if x != nil { + return x.OpeningFeeParamsMenu + } + return nil +} + +type OpeningFeeParams struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + MinMsat uint64 `protobuf:"varint,1,opt,name=min_msat,json=minMsat,proto3" json:"min_msat,omitempty"` + Proportional uint32 `protobuf:"varint,2,opt,name=proportional,proto3" json:"proportional,omitempty"` + ValidUntil string `protobuf:"bytes,3,opt,name=valid_until,json=validUntil,proto3" json:"valid_until,omitempty"` + // The channel can be closed if not used this duration in blocks. + MaxIdleTime uint32 `protobuf:"varint,4,opt,name=max_idle_time,json=maxIdleTime,proto3" json:"max_idle_time,omitempty"` + MaxClientToSelfDelay uint32 `protobuf:"varint,5,opt,name=max_client_to_self_delay,json=maxClientToSelfDelay,proto3" json:"max_client_to_self_delay,omitempty"` + Promise string `protobuf:"bytes,6,opt,name=promise,proto3" json:"promise,omitempty"` +} + +func (x *OpeningFeeParams) Reset() { + *x = OpeningFeeParams{} + if protoimpl.UnsafeEnabled { + mi := &file_lspd_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *OpeningFeeParams) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*OpeningFeeParams) ProtoMessage() {} + +func (x *OpeningFeeParams) ProtoReflect() protoreflect.Message { + mi := &file_lspd_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use OpeningFeeParams.ProtoReflect.Descriptor instead. +func (*OpeningFeeParams) Descriptor() ([]byte, []int) { + return file_lspd_proto_rawDescGZIP(), []int{2} +} + +func (x *OpeningFeeParams) GetMinMsat() uint64 { + if x != nil { + return x.MinMsat + } + return 0 +} + +func (x *OpeningFeeParams) GetProportional() uint32 { + if x != nil { + return x.Proportional + } + return 0 +} + +func (x *OpeningFeeParams) GetValidUntil() string { + if x != nil { + return x.ValidUntil + } + return "" +} + +func (x *OpeningFeeParams) GetMaxIdleTime() uint32 { + if x != nil { + return x.MaxIdleTime + } + return 0 +} + +func (x *OpeningFeeParams) GetMaxClientToSelfDelay() uint32 { + if x != nil { + return x.MaxClientToSelfDelay + } + return 0 +} + +func (x *OpeningFeeParams) GetPromise() string { + if x != nil { + return x.Promise + } + return "" +} + type OpenChannelRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -237,7 +340,7 @@ type OpenChannelRequest struct { func (x *OpenChannelRequest) Reset() { *x = OpenChannelRequest{} if protoimpl.UnsafeEnabled { - mi := &file_lspd_proto_msgTypes[2] + mi := &file_lspd_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -250,7 +353,7 @@ func (x *OpenChannelRequest) String() string { func (*OpenChannelRequest) ProtoMessage() {} func (x *OpenChannelRequest) ProtoReflect() protoreflect.Message { - mi := &file_lspd_proto_msgTypes[2] + mi := &file_lspd_proto_msgTypes[3] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -263,7 +366,7 @@ func (x *OpenChannelRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use OpenChannelRequest.ProtoReflect.Descriptor instead. func (*OpenChannelRequest) Descriptor() ([]byte, []int) { - return file_lspd_proto_rawDescGZIP(), []int{2} + return file_lspd_proto_rawDescGZIP(), []int{3} } func (x *OpenChannelRequest) GetPubkey() string { @@ -287,7 +390,7 @@ type OpenChannelReply struct { func (x *OpenChannelReply) Reset() { *x = OpenChannelReply{} if protoimpl.UnsafeEnabled { - mi := &file_lspd_proto_msgTypes[3] + mi := &file_lspd_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -300,7 +403,7 @@ func (x *OpenChannelReply) String() string { func (*OpenChannelReply) ProtoMessage() {} func (x *OpenChannelReply) ProtoReflect() protoreflect.Message { - mi := &file_lspd_proto_msgTypes[3] + mi := &file_lspd_proto_msgTypes[4] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -313,7 +416,7 @@ func (x *OpenChannelReply) ProtoReflect() protoreflect.Message { // Deprecated: Use OpenChannelReply.ProtoReflect.Descriptor instead. func (*OpenChannelReply) Descriptor() ([]byte, []int) { - return file_lspd_proto_rawDescGZIP(), []int{3} + return file_lspd_proto_rawDescGZIP(), []int{4} } func (x *OpenChannelReply) GetTxHash() string { @@ -341,7 +444,7 @@ type RegisterPaymentRequest struct { func (x *RegisterPaymentRequest) Reset() { *x = RegisterPaymentRequest{} if protoimpl.UnsafeEnabled { - mi := &file_lspd_proto_msgTypes[4] + mi := &file_lspd_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -354,7 +457,7 @@ func (x *RegisterPaymentRequest) String() string { func (*RegisterPaymentRequest) ProtoMessage() {} func (x *RegisterPaymentRequest) ProtoReflect() protoreflect.Message { - mi := &file_lspd_proto_msgTypes[4] + mi := &file_lspd_proto_msgTypes[5] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -367,7 +470,7 @@ func (x *RegisterPaymentRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RegisterPaymentRequest.ProtoReflect.Descriptor instead. func (*RegisterPaymentRequest) Descriptor() ([]byte, []int) { - return file_lspd_proto_rawDescGZIP(), []int{4} + return file_lspd_proto_rawDescGZIP(), []int{5} } func (x *RegisterPaymentRequest) GetBlob() []byte { @@ -386,7 +489,7 @@ type RegisterPaymentReply struct { func (x *RegisterPaymentReply) Reset() { *x = RegisterPaymentReply{} if protoimpl.UnsafeEnabled { - mi := &file_lspd_proto_msgTypes[5] + mi := &file_lspd_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -399,7 +502,7 @@ func (x *RegisterPaymentReply) String() string { func (*RegisterPaymentReply) ProtoMessage() {} func (x *RegisterPaymentReply) ProtoReflect() protoreflect.Message { - mi := &file_lspd_proto_msgTypes[5] + mi := &file_lspd_proto_msgTypes[6] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -412,7 +515,7 @@ func (x *RegisterPaymentReply) ProtoReflect() protoreflect.Message { // Deprecated: Use RegisterPaymentReply.ProtoReflect.Descriptor instead. func (*RegisterPaymentReply) Descriptor() ([]byte, []int) { - return file_lspd_proto_rawDescGZIP(), []int{5} + return file_lspd_proto_rawDescGZIP(), []int{6} } type PaymentInformation struct { @@ -420,18 +523,19 @@ type PaymentInformation struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - PaymentHash []byte `protobuf:"bytes,1,opt,name=payment_hash,json=paymentHash,proto3" json:"payment_hash,omitempty"` - PaymentSecret []byte `protobuf:"bytes,2,opt,name=payment_secret,json=paymentSecret,proto3" json:"payment_secret,omitempty"` - Destination []byte `protobuf:"bytes,3,opt,name=destination,proto3" json:"destination,omitempty"` - IncomingAmountMsat int64 `protobuf:"varint,4,opt,name=incoming_amount_msat,json=incomingAmountMsat,proto3" json:"incoming_amount_msat,omitempty"` - OutgoingAmountMsat int64 `protobuf:"varint,5,opt,name=outgoing_amount_msat,json=outgoingAmountMsat,proto3" json:"outgoing_amount_msat,omitempty"` - Tag string `protobuf:"bytes,6,opt,name=tag,proto3" json:"tag,omitempty"` + PaymentHash []byte `protobuf:"bytes,1,opt,name=payment_hash,json=paymentHash,proto3" json:"payment_hash,omitempty"` + PaymentSecret []byte `protobuf:"bytes,2,opt,name=payment_secret,json=paymentSecret,proto3" json:"payment_secret,omitempty"` + Destination []byte `protobuf:"bytes,3,opt,name=destination,proto3" json:"destination,omitempty"` + IncomingAmountMsat int64 `protobuf:"varint,4,opt,name=incoming_amount_msat,json=incomingAmountMsat,proto3" json:"incoming_amount_msat,omitempty"` + OutgoingAmountMsat int64 `protobuf:"varint,5,opt,name=outgoing_amount_msat,json=outgoingAmountMsat,proto3" json:"outgoing_amount_msat,omitempty"` + Tag string `protobuf:"bytes,6,opt,name=tag,proto3" json:"tag,omitempty"` + OpeningFeeParams *OpeningFeeParams `protobuf:"bytes,7,opt,name=opening_fee_params,json=openingFeeParams,proto3" json:"opening_fee_params,omitempty"` } func (x *PaymentInformation) Reset() { *x = PaymentInformation{} if protoimpl.UnsafeEnabled { - mi := &file_lspd_proto_msgTypes[6] + mi := &file_lspd_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -444,7 +548,7 @@ func (x *PaymentInformation) String() string { func (*PaymentInformation) ProtoMessage() {} func (x *PaymentInformation) ProtoReflect() protoreflect.Message { - mi := &file_lspd_proto_msgTypes[6] + mi := &file_lspd_proto_msgTypes[7] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -457,7 +561,7 @@ func (x *PaymentInformation) ProtoReflect() protoreflect.Message { // Deprecated: Use PaymentInformation.ProtoReflect.Descriptor instead. func (*PaymentInformation) Descriptor() ([]byte, []int) { - return file_lspd_proto_rawDescGZIP(), []int{6} + return file_lspd_proto_rawDescGZIP(), []int{7} } func (x *PaymentInformation) GetPaymentHash() []byte { @@ -502,6 +606,13 @@ func (x *PaymentInformation) GetTag() string { return "" } +func (x *PaymentInformation) GetOpeningFeeParams() *OpeningFeeParams { + if x != nil { + return x.OpeningFeeParams + } + return nil +} + type Encrypted struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -513,7 +624,7 @@ type Encrypted struct { func (x *Encrypted) Reset() { *x = Encrypted{} if protoimpl.UnsafeEnabled { - mi := &file_lspd_proto_msgTypes[7] + mi := &file_lspd_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -526,7 +637,7 @@ func (x *Encrypted) String() string { func (*Encrypted) ProtoMessage() {} func (x *Encrypted) ProtoReflect() protoreflect.Message { - mi := &file_lspd_proto_msgTypes[7] + mi := &file_lspd_proto_msgTypes[8] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -539,7 +650,7 @@ func (x *Encrypted) ProtoReflect() protoreflect.Message { // Deprecated: Use Encrypted.ProtoReflect.Descriptor instead. func (*Encrypted) Descriptor() ([]byte, []int) { - return file_lspd_proto_rawDescGZIP(), []int{7} + return file_lspd_proto_rawDescGZIP(), []int{8} } func (x *Encrypted) GetData() []byte { @@ -562,7 +673,7 @@ type Signed struct { func (x *Signed) Reset() { *x = Signed{} if protoimpl.UnsafeEnabled { - mi := &file_lspd_proto_msgTypes[8] + mi := &file_lspd_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -575,7 +686,7 @@ func (x *Signed) String() string { func (*Signed) ProtoMessage() {} func (x *Signed) ProtoReflect() protoreflect.Message { - mi := &file_lspd_proto_msgTypes[8] + mi := &file_lspd_proto_msgTypes[9] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -588,7 +699,7 @@ func (x *Signed) ProtoReflect() protoreflect.Message { // Deprecated: Use Signed.ProtoReflect.Descriptor instead. func (*Signed) Descriptor() ([]byte, []int) { - return file_lspd_proto_rawDescGZIP(), []int{8} + return file_lspd_proto_rawDescGZIP(), []int{9} } func (x *Signed) GetData() []byte { @@ -625,7 +736,7 @@ type CheckChannelsRequest struct { func (x *CheckChannelsRequest) Reset() { *x = CheckChannelsRequest{} if protoimpl.UnsafeEnabled { - mi := &file_lspd_proto_msgTypes[9] + mi := &file_lspd_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -638,7 +749,7 @@ func (x *CheckChannelsRequest) String() string { func (*CheckChannelsRequest) ProtoMessage() {} func (x *CheckChannelsRequest) ProtoReflect() protoreflect.Message { - mi := &file_lspd_proto_msgTypes[9] + mi := &file_lspd_proto_msgTypes[10] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -651,7 +762,7 @@ func (x *CheckChannelsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CheckChannelsRequest.ProtoReflect.Descriptor instead. func (*CheckChannelsRequest) Descriptor() ([]byte, []int) { - return file_lspd_proto_rawDescGZIP(), []int{9} + return file_lspd_proto_rawDescGZIP(), []int{10} } func (x *CheckChannelsRequest) GetEncryptPubkey() []byte { @@ -687,7 +798,7 @@ type CheckChannelsReply struct { func (x *CheckChannelsReply) Reset() { *x = CheckChannelsReply{} if protoimpl.UnsafeEnabled { - mi := &file_lspd_proto_msgTypes[10] + mi := &file_lspd_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -700,7 +811,7 @@ func (x *CheckChannelsReply) String() string { func (*CheckChannelsReply) ProtoMessage() {} func (x *CheckChannelsReply) ProtoReflect() protoreflect.Message { - mi := &file_lspd_proto_msgTypes[10] + mi := &file_lspd_proto_msgTypes[11] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -713,7 +824,7 @@ func (x *CheckChannelsReply) ProtoReflect() protoreflect.Message { // Deprecated: Use CheckChannelsReply.ProtoReflect.Descriptor instead. func (*CheckChannelsReply) Descriptor() ([]byte, []int) { - return file_lspd_proto_rawDescGZIP(), []int{10} + return file_lspd_proto_rawDescGZIP(), []int{11} } func (x *CheckChannelsReply) GetNotFakeChannels() map[string]uint64 { @@ -737,7 +848,7 @@ var file_lspd_proto_rawDesc = []byte{ 0x70, 0x64, 0x22, 0x33, 0x0a, 0x19, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x06, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x22, 0xf9, 0x03, 0x0a, 0x17, 0x43, 0x68, 0x61, 0x6e, + 0x06, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x22, 0xd4, 0x04, 0x0a, 0x17, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x75, 0x62, 0x6b, 0x65, @@ -757,123 +868,148 @@ var file_lspd_proto_rawDesc = []byte{ 0x6d, 0x65, 0x5f, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x12, 0x24, 0x0a, 0x0d, 0x6d, 0x69, 0x6e, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x6d, 0x69, 0x6e, 0x5f, 0x68, 0x74, 0x6c, 0x63, 0x5f, 0x6d, - 0x73, 0x61, 0x74, 0x12, 0x32, 0x0a, 0x15, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x66, + 0x73, 0x61, 0x74, 0x12, 0x36, 0x0a, 0x15, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x66, 0x65, 0x65, 0x5f, 0x70, 0x65, 0x72, 0x6d, 0x79, 0x72, 0x69, 0x61, 0x64, 0x18, 0x0a, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x13, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x46, 0x65, 0x65, 0x50, 0x65, - 0x72, 0x6d, 0x79, 0x72, 0x69, 0x61, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x6c, 0x73, 0x70, 0x5f, 0x70, - 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x6c, 0x73, 0x70, - 0x50, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x12, 0x32, 0x0a, 0x15, 0x6d, 0x61, 0x78, 0x5f, 0x69, 0x6e, - 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, - 0x0c, 0x20, 0x01, 0x28, 0x03, 0x52, 0x13, 0x6d, 0x61, 0x78, 0x49, 0x6e, 0x61, 0x63, 0x74, 0x69, - 0x76, 0x65, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x37, 0x0a, 0x18, 0x63, 0x68, - 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x6d, 0x69, 0x6e, 0x69, 0x6d, 0x75, 0x6d, 0x5f, 0x66, 0x65, - 0x65, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x03, 0x52, 0x15, 0x63, 0x68, - 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x4d, 0x69, 0x6e, 0x69, 0x6d, 0x75, 0x6d, 0x46, 0x65, 0x65, 0x4d, - 0x73, 0x61, 0x74, 0x22, 0x2c, 0x0a, 0x12, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, - 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x75, 0x62, - 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x75, 0x62, 0x6b, 0x65, - 0x79, 0x22, 0x50, 0x0a, 0x10, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, - 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x74, 0x78, 0x5f, 0x68, 0x61, 0x73, 0x68, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x74, 0x78, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x12, - 0x22, 0x0a, 0x0c, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0c, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x5f, 0x69, 0x6e, - 0x64, 0x65, 0x78, 0x22, 0x2c, 0x0a, 0x16, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x50, - 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, - 0x04, 0x62, 0x6c, 0x6f, 0x62, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x62, 0x6c, 0x6f, - 0x62, 0x22, 0x16, 0x0a, 0x14, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x50, 0x61, 0x79, - 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0xf6, 0x01, 0x0a, 0x12, 0x50, 0x61, - 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x68, 0x61, 0x73, 0x68, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x48, - 0x61, 0x73, 0x68, 0x12, 0x25, 0x0a, 0x0e, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x73, - 0x65, 0x63, 0x72, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x70, 0x61, 0x79, - 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, - 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, - 0x0b, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x30, 0x0a, 0x14, - 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, - 0x6d, 0x73, 0x61, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x12, 0x69, 0x6e, 0x63, 0x6f, - 0x6d, 0x69, 0x6e, 0x67, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x30, - 0x0a, 0x14, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, - 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x12, 0x6f, 0x75, - 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x4d, 0x73, 0x61, 0x74, - 0x12, 0x10, 0x0a, 0x03, 0x74, 0x61, 0x67, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x74, - 0x61, 0x67, 0x22, 0x1f, 0x0a, 0x09, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x12, - 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, - 0x61, 0x74, 0x61, 0x22, 0x52, 0x0a, 0x06, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x12, 0x12, 0x0a, - 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61, 0x74, - 0x61, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x0c, 0x52, 0x06, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x69, 0x67, - 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x69, - 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0x86, 0x03, 0x0a, 0x14, 0x43, 0x68, 0x65, 0x63, - 0x6b, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x25, 0x0a, 0x0e, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x5f, 0x70, 0x75, 0x62, 0x6b, - 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, - 0x74, 0x50, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x12, 0x51, 0x0a, 0x0d, 0x66, 0x61, 0x6b, 0x65, 0x5f, - 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2c, - 0x2e, 0x6c, 0x73, 0x70, 0x64, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x43, 0x68, 0x61, 0x6e, 0x6e, - 0x65, 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x46, 0x61, 0x6b, 0x65, 0x43, - 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0c, 0x66, 0x61, - 0x6b, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x12, 0x6a, 0x0a, 0x16, 0x77, 0x61, - 0x69, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x5f, 0x63, 0x68, 0x61, 0x6e, - 0x6e, 0x65, 0x6c, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x34, 0x2e, 0x6c, 0x73, 0x70, + 0x28, 0x03, 0x42, 0x02, 0x18, 0x01, 0x52, 0x13, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x46, + 0x65, 0x65, 0x50, 0x65, 0x72, 0x6d, 0x79, 0x72, 0x69, 0x61, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x6c, + 0x73, 0x70, 0x5f, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0c, 0x52, + 0x09, 0x6c, 0x73, 0x70, 0x50, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x12, 0x36, 0x0a, 0x15, 0x6d, 0x61, + 0x78, 0x5f, 0x69, 0x6e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x64, 0x75, 0x72, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x03, 0x42, 0x02, 0x18, 0x01, 0x52, 0x13, 0x6d, + 0x61, 0x78, 0x49, 0x6e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x12, 0x3b, 0x0a, 0x18, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x6d, 0x69, + 0x6e, 0x69, 0x6d, 0x75, 0x6d, 0x5f, 0x66, 0x65, 0x65, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x0d, + 0x20, 0x01, 0x28, 0x03, 0x42, 0x02, 0x18, 0x01, 0x52, 0x15, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, + 0x6c, 0x4d, 0x69, 0x6e, 0x69, 0x6d, 0x75, 0x6d, 0x46, 0x65, 0x65, 0x4d, 0x73, 0x61, 0x74, 0x12, + 0x4d, 0x0a, 0x17, 0x6f, 0x70, 0x65, 0x6e, 0x69, 0x6e, 0x67, 0x5f, 0x66, 0x65, 0x65, 0x5f, 0x70, + 0x61, 0x72, 0x61, 0x6d, 0x73, 0x5f, 0x6d, 0x65, 0x6e, 0x75, 0x18, 0x0e, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x16, 0x2e, 0x6c, 0x73, 0x70, 0x64, 0x2e, 0x4f, 0x70, 0x65, 0x6e, 0x69, 0x6e, 0x67, 0x46, + 0x65, 0x65, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x52, 0x14, 0x6f, 0x70, 0x65, 0x6e, 0x69, 0x6e, + 0x67, 0x46, 0x65, 0x65, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x4d, 0x65, 0x6e, 0x75, 0x22, 0xe8, + 0x01, 0x0a, 0x10, 0x4f, 0x70, 0x65, 0x6e, 0x69, 0x6e, 0x67, 0x46, 0x65, 0x65, 0x50, 0x61, 0x72, + 0x61, 0x6d, 0x73, 0x12, 0x19, 0x0a, 0x08, 0x6d, 0x69, 0x6e, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x6d, 0x69, 0x6e, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x22, + 0x0a, 0x0c, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0c, 0x70, 0x72, 0x6f, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6f, 0x6e, + 0x61, 0x6c, 0x12, 0x1f, 0x0a, 0x0b, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x5f, 0x75, 0x6e, 0x74, 0x69, + 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x55, 0x6e, + 0x74, 0x69, 0x6c, 0x12, 0x22, 0x0a, 0x0d, 0x6d, 0x61, 0x78, 0x5f, 0x69, 0x64, 0x6c, 0x65, 0x5f, + 0x74, 0x69, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x6d, 0x61, 0x78, 0x49, + 0x64, 0x6c, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x36, 0x0a, 0x18, 0x6d, 0x61, 0x78, 0x5f, 0x63, + 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x6f, 0x5f, 0x73, 0x65, 0x6c, 0x66, 0x5f, 0x64, 0x65, + 0x6c, 0x61, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x14, 0x6d, 0x61, 0x78, 0x43, 0x6c, + 0x69, 0x65, 0x6e, 0x74, 0x54, 0x6f, 0x53, 0x65, 0x6c, 0x66, 0x44, 0x65, 0x6c, 0x61, 0x79, 0x12, + 0x18, 0x0a, 0x07, 0x70, 0x72, 0x6f, 0x6d, 0x69, 0x73, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x07, 0x70, 0x72, 0x6f, 0x6d, 0x69, 0x73, 0x65, 0x22, 0x2c, 0x0a, 0x12, 0x4f, 0x70, 0x65, + 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x16, 0x0a, 0x06, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x06, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x22, 0x50, 0x0a, 0x10, 0x4f, 0x70, 0x65, 0x6e, 0x43, + 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x74, + 0x78, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x74, 0x78, + 0x5f, 0x68, 0x61, 0x73, 0x68, 0x12, 0x22, 0x0a, 0x0c, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x5f, + 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0c, 0x6f, 0x75, 0x74, + 0x70, 0x75, 0x74, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x22, 0x2c, 0x0a, 0x16, 0x52, 0x65, 0x67, + 0x69, 0x73, 0x74, 0x65, 0x72, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x62, 0x6c, 0x6f, 0x62, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x04, 0x62, 0x6c, 0x6f, 0x62, 0x22, 0x16, 0x0a, 0x14, 0x52, 0x65, 0x67, 0x69, 0x73, + 0x74, 0x65, 0x72, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, + 0xbc, 0x02, 0x0a, 0x12, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x72, + 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, + 0x74, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x70, 0x61, + 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x48, 0x61, 0x73, 0x68, 0x12, 0x25, 0x0a, 0x0e, 0x70, 0x61, 0x79, + 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x0d, 0x70, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, + 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x12, 0x30, 0x0a, 0x14, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x5f, 0x61, + 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x12, 0x69, 0x6e, 0x63, 0x6f, 0x6d, 0x69, 0x6e, 0x67, 0x41, 0x6d, 0x6f, 0x75, 0x6e, 0x74, + 0x4d, 0x73, 0x61, 0x74, 0x12, 0x30, 0x0a, 0x14, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, + 0x5f, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x6d, 0x73, 0x61, 0x74, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x12, 0x6f, 0x75, 0x74, 0x67, 0x6f, 0x69, 0x6e, 0x67, 0x41, 0x6d, 0x6f, 0x75, + 0x6e, 0x74, 0x4d, 0x73, 0x61, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x61, 0x67, 0x18, 0x06, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x03, 0x74, 0x61, 0x67, 0x12, 0x44, 0x0a, 0x12, 0x6f, 0x70, 0x65, 0x6e, + 0x69, 0x6e, 0x67, 0x5f, 0x66, 0x65, 0x65, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x18, 0x07, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6c, 0x73, 0x70, 0x64, 0x2e, 0x4f, 0x70, 0x65, 0x6e, + 0x69, 0x6e, 0x67, 0x46, 0x65, 0x65, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x52, 0x10, 0x6f, 0x70, + 0x65, 0x6e, 0x69, 0x6e, 0x67, 0x46, 0x65, 0x65, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x22, 0x1f, + 0x0a, 0x09, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x64, + 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, + 0x52, 0x0a, 0x06, 0x53, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, + 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x12, 0x16, 0x0a, + 0x06, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x70, + 0x75, 0x62, 0x6b, 0x65, 0x79, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, + 0x72, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, + 0x75, 0x72, 0x65, 0x22, 0x86, 0x03, 0x0a, 0x14, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x43, 0x68, 0x61, + 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x25, 0x0a, 0x0e, + 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x5f, 0x70, 0x75, 0x62, 0x6b, 0x65, 0x79, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0d, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x50, 0x75, 0x62, + 0x6b, 0x65, 0x79, 0x12, 0x51, 0x0a, 0x0d, 0x66, 0x61, 0x6b, 0x65, 0x5f, 0x63, 0x68, 0x61, 0x6e, + 0x6e, 0x65, 0x6c, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x6c, 0x73, 0x70, 0x64, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x57, 0x61, 0x69, 0x74, 0x69, 0x6e, 0x67, 0x43, 0x6c, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x46, 0x61, 0x6b, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, + 0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0c, 0x66, 0x61, 0x6b, 0x65, 0x43, 0x68, + 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x12, 0x6a, 0x0a, 0x16, 0x77, 0x61, 0x69, 0x74, 0x69, 0x6e, + 0x67, 0x5f, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, + 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x34, 0x2e, 0x6c, 0x73, 0x70, 0x64, 0x2e, 0x43, 0x68, + 0x65, 0x63, 0x6b, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x2e, 0x57, 0x61, 0x69, 0x74, 0x69, 0x6e, 0x67, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x43, + 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x14, 0x77, 0x61, + 0x69, 0x74, 0x69, 0x6e, 0x67, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, + 0x6c, 0x73, 0x1a, 0x3f, 0x0a, 0x11, 0x46, 0x61, 0x6b, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, + 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, + 0x02, 0x38, 0x01, 0x1a, 0x47, 0x0a, 0x19, 0x57, 0x61, 0x69, 0x74, 0x69, 0x6e, 0x67, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, - 0x52, 0x14, 0x77, 0x61, 0x69, 0x74, 0x69, 0x6e, 0x67, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x43, 0x68, - 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x1a, 0x3f, 0x0a, 0x11, 0x46, 0x61, 0x6b, 0x65, 0x43, 0x68, - 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, - 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, - 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x76, 0x61, - 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x47, 0x0a, 0x19, 0x57, 0x61, 0x69, 0x74, 0x69, - 0x6e, 0x67, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x45, - 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, - 0x22, 0xcd, 0x02, 0x0a, 0x12, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, - 0x6c, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x59, 0x0a, 0x11, 0x6e, 0x6f, 0x74, 0x5f, 0x66, - 0x61, 0x6b, 0x65, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x18, 0x01, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x6c, 0x73, 0x70, 0x64, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x43, - 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x2e, 0x4e, 0x6f, 0x74, - 0x46, 0x61, 0x6b, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, - 0x79, 0x52, 0x0f, 0x6e, 0x6f, 0x74, 0x46, 0x61, 0x6b, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, - 0x6c, 0x73, 0x12, 0x55, 0x0a, 0x0f, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x5f, 0x63, 0x68, 0x61, - 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x6c, 0x73, - 0x70, 0x64, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, - 0x52, 0x65, 0x70, 0x6c, 0x79, 0x2e, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x43, 0x68, 0x61, 0x6e, - 0x6e, 0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0e, 0x63, 0x6c, 0x6f, 0x73, 0x65, - 0x64, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x1a, 0x42, 0x0a, 0x14, 0x4e, 0x6f, 0x74, - 0x46, 0x61, 0x6b, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, - 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, - 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x04, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x41, 0x0a, - 0x13, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x45, - 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, - 0x32, 0xae, 0x02, 0x0a, 0x0d, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x4f, 0x70, 0x65, 0x6e, - 0x65, 0x72, 0x12, 0x56, 0x0a, 0x12, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x49, 0x6e, 0x66, - 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1f, 0x2e, 0x6c, 0x73, 0x70, 0x64, 0x2e, - 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x6c, 0x73, 0x70, 0x64, - 0x2e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x41, 0x0a, 0x0b, 0x4f, 0x70, - 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x18, 0x2e, 0x6c, 0x73, 0x70, 0x64, - 0x2e, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x6c, 0x73, 0x70, 0x64, 0x2e, 0x4f, 0x70, 0x65, 0x6e, 0x43, - 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x4d, 0x0a, - 0x0f, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, - 0x12, 0x1c, 0x2e, 0x6c, 0x73, 0x70, 0x64, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, - 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, - 0x2e, 0x6c, 0x73, 0x70, 0x64, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x50, 0x61, - 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x33, 0x0a, 0x0d, - 0x43, 0x68, 0x65, 0x63, 0x6b, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x12, 0x0f, 0x2e, - 0x6c, 0x73, 0x70, 0x64, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x1a, 0x0f, - 0x2e, 0x6c, 0x73, 0x70, 0x64, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x22, - 0x00, 0x42, 0x3a, 0x0a, 0x14, 0x69, 0x6f, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x6c, 0x73, 0x70, - 0x64, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x42, 0x09, 0x4c, 0x73, 0x70, 0x64, 0x50, - 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x15, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, - 0x6f, 0x6d, 0x2f, 0x62, 0x72, 0x65, 0x65, 0x7a, 0x2f, 0x6c, 0x73, 0x70, 0x64, 0x62, 0x06, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, + 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x04, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xcd, 0x02, 0x0a, + 0x12, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, + 0x70, 0x6c, 0x79, 0x12, 0x59, 0x0a, 0x11, 0x6e, 0x6f, 0x74, 0x5f, 0x66, 0x61, 0x6b, 0x65, 0x5f, + 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2d, + 0x2e, 0x6c, 0x73, 0x70, 0x64, 0x2e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x43, 0x68, 0x61, 0x6e, 0x6e, + 0x65, 0x6c, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x2e, 0x4e, 0x6f, 0x74, 0x46, 0x61, 0x6b, 0x65, + 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0f, 0x6e, + 0x6f, 0x74, 0x46, 0x61, 0x6b, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x12, 0x55, + 0x0a, 0x0f, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, + 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x6c, 0x73, 0x70, 0x64, 0x2e, 0x43, + 0x68, 0x65, 0x63, 0x6b, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x52, 0x65, 0x70, 0x6c, + 0x79, 0x2e, 0x43, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, + 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0e, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x43, 0x68, 0x61, + 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x1a, 0x42, 0x0a, 0x14, 0x4e, 0x6f, 0x74, 0x46, 0x61, 0x6b, 0x65, + 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, + 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, + 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x41, 0x0a, 0x13, 0x43, 0x6c, 0x6f, + 0x73, 0x65, 0x64, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, + 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, + 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x04, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x32, 0xae, 0x02, 0x0a, + 0x0d, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x4f, 0x70, 0x65, 0x6e, 0x65, 0x72, 0x12, 0x56, + 0x0a, 0x12, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1f, 0x2e, 0x6c, 0x73, 0x70, 0x64, 0x2e, 0x43, 0x68, 0x61, 0x6e, + 0x6e, 0x65, 0x6c, 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x6c, 0x73, 0x70, 0x64, 0x2e, 0x43, 0x68, 0x61, + 0x6e, 0x6e, 0x65, 0x6c, 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, + 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x41, 0x0a, 0x0b, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x68, + 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x18, 0x2e, 0x6c, 0x73, 0x70, 0x64, 0x2e, 0x4f, 0x70, 0x65, + 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x16, 0x2e, 0x6c, 0x73, 0x70, 0x64, 0x2e, 0x4f, 0x70, 0x65, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, + 0x65, 0x6c, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x4d, 0x0a, 0x0f, 0x52, 0x65, 0x67, + 0x69, 0x73, 0x74, 0x65, 0x72, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x1c, 0x2e, 0x6c, + 0x73, 0x70, 0x64, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x50, 0x61, 0x79, 0x6d, + 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6c, 0x73, 0x70, + 0x64, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x50, 0x61, 0x79, 0x6d, 0x65, 0x6e, + 0x74, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x33, 0x0a, 0x0d, 0x43, 0x68, 0x65, 0x63, + 0x6b, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x12, 0x0f, 0x2e, 0x6c, 0x73, 0x70, 0x64, + 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x1a, 0x0f, 0x2e, 0x6c, 0x73, 0x70, + 0x64, 0x2e, 0x45, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x65, 0x64, 0x22, 0x00, 0x42, 0x3a, 0x0a, + 0x14, 0x69, 0x6f, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x6c, 0x73, 0x70, 0x64, 0x2e, 0x73, 0x65, + 0x72, 0x76, 0x69, 0x63, 0x65, 0x42, 0x09, 0x4c, 0x73, 0x70, 0x64, 0x50, 0x72, 0x6f, 0x74, 0x6f, + 0x50, 0x01, 0x5a, 0x15, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x62, + 0x72, 0x65, 0x65, 0x7a, 0x2f, 0x6c, 0x73, 0x70, 0x64, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x33, } var ( @@ -888,42 +1024,45 @@ func file_lspd_proto_rawDescGZIP() []byte { return file_lspd_proto_rawDescData } -var file_lspd_proto_msgTypes = make([]protoimpl.MessageInfo, 15) +var file_lspd_proto_msgTypes = make([]protoimpl.MessageInfo, 16) var file_lspd_proto_goTypes = []interface{}{ (*ChannelInformationRequest)(nil), // 0: lspd.ChannelInformationRequest (*ChannelInformationReply)(nil), // 1: lspd.ChannelInformationReply - (*OpenChannelRequest)(nil), // 2: lspd.OpenChannelRequest - (*OpenChannelReply)(nil), // 3: lspd.OpenChannelReply - (*RegisterPaymentRequest)(nil), // 4: lspd.RegisterPaymentRequest - (*RegisterPaymentReply)(nil), // 5: lspd.RegisterPaymentReply - (*PaymentInformation)(nil), // 6: lspd.PaymentInformation - (*Encrypted)(nil), // 7: lspd.Encrypted - (*Signed)(nil), // 8: lspd.Signed - (*CheckChannelsRequest)(nil), // 9: lspd.CheckChannelsRequest - (*CheckChannelsReply)(nil), // 10: lspd.CheckChannelsReply - nil, // 11: lspd.CheckChannelsRequest.FakeChannelsEntry - nil, // 12: lspd.CheckChannelsRequest.WaitingCloseChannelsEntry - nil, // 13: lspd.CheckChannelsReply.NotFakeChannelsEntry - nil, // 14: lspd.CheckChannelsReply.ClosedChannelsEntry + (*OpeningFeeParams)(nil), // 2: lspd.OpeningFeeParams + (*OpenChannelRequest)(nil), // 3: lspd.OpenChannelRequest + (*OpenChannelReply)(nil), // 4: lspd.OpenChannelReply + (*RegisterPaymentRequest)(nil), // 5: lspd.RegisterPaymentRequest + (*RegisterPaymentReply)(nil), // 6: lspd.RegisterPaymentReply + (*PaymentInformation)(nil), // 7: lspd.PaymentInformation + (*Encrypted)(nil), // 8: lspd.Encrypted + (*Signed)(nil), // 9: lspd.Signed + (*CheckChannelsRequest)(nil), // 10: lspd.CheckChannelsRequest + (*CheckChannelsReply)(nil), // 11: lspd.CheckChannelsReply + nil, // 12: lspd.CheckChannelsRequest.FakeChannelsEntry + nil, // 13: lspd.CheckChannelsRequest.WaitingCloseChannelsEntry + nil, // 14: lspd.CheckChannelsReply.NotFakeChannelsEntry + nil, // 15: lspd.CheckChannelsReply.ClosedChannelsEntry } var file_lspd_proto_depIdxs = []int32{ - 11, // 0: lspd.CheckChannelsRequest.fake_channels:type_name -> lspd.CheckChannelsRequest.FakeChannelsEntry - 12, // 1: lspd.CheckChannelsRequest.waiting_close_channels:type_name -> lspd.CheckChannelsRequest.WaitingCloseChannelsEntry - 13, // 2: lspd.CheckChannelsReply.not_fake_channels:type_name -> lspd.CheckChannelsReply.NotFakeChannelsEntry - 14, // 3: lspd.CheckChannelsReply.closed_channels:type_name -> lspd.CheckChannelsReply.ClosedChannelsEntry - 0, // 4: lspd.ChannelOpener.ChannelInformation:input_type -> lspd.ChannelInformationRequest - 2, // 5: lspd.ChannelOpener.OpenChannel:input_type -> lspd.OpenChannelRequest - 4, // 6: lspd.ChannelOpener.RegisterPayment:input_type -> lspd.RegisterPaymentRequest - 7, // 7: lspd.ChannelOpener.CheckChannels:input_type -> lspd.Encrypted - 1, // 8: lspd.ChannelOpener.ChannelInformation:output_type -> lspd.ChannelInformationReply - 3, // 9: lspd.ChannelOpener.OpenChannel:output_type -> lspd.OpenChannelReply - 5, // 10: lspd.ChannelOpener.RegisterPayment:output_type -> lspd.RegisterPaymentReply - 7, // 11: lspd.ChannelOpener.CheckChannels:output_type -> lspd.Encrypted - 8, // [8:12] is the sub-list for method output_type - 4, // [4:8] is the sub-list for method input_type - 4, // [4:4] is the sub-list for extension type_name - 4, // [4:4] is the sub-list for extension extendee - 0, // [0:4] is the sub-list for field type_name + 2, // 0: lspd.ChannelInformationReply.opening_fee_params_menu:type_name -> lspd.OpeningFeeParams + 2, // 1: lspd.PaymentInformation.opening_fee_params:type_name -> lspd.OpeningFeeParams + 12, // 2: lspd.CheckChannelsRequest.fake_channels:type_name -> lspd.CheckChannelsRequest.FakeChannelsEntry + 13, // 3: lspd.CheckChannelsRequest.waiting_close_channels:type_name -> lspd.CheckChannelsRequest.WaitingCloseChannelsEntry + 14, // 4: lspd.CheckChannelsReply.not_fake_channels:type_name -> lspd.CheckChannelsReply.NotFakeChannelsEntry + 15, // 5: lspd.CheckChannelsReply.closed_channels:type_name -> lspd.CheckChannelsReply.ClosedChannelsEntry + 0, // 6: lspd.ChannelOpener.ChannelInformation:input_type -> lspd.ChannelInformationRequest + 3, // 7: lspd.ChannelOpener.OpenChannel:input_type -> lspd.OpenChannelRequest + 5, // 8: lspd.ChannelOpener.RegisterPayment:input_type -> lspd.RegisterPaymentRequest + 8, // 9: lspd.ChannelOpener.CheckChannels:input_type -> lspd.Encrypted + 1, // 10: lspd.ChannelOpener.ChannelInformation:output_type -> lspd.ChannelInformationReply + 4, // 11: lspd.ChannelOpener.OpenChannel:output_type -> lspd.OpenChannelReply + 6, // 12: lspd.ChannelOpener.RegisterPayment:output_type -> lspd.RegisterPaymentReply + 8, // 13: lspd.ChannelOpener.CheckChannels:output_type -> lspd.Encrypted + 10, // [10:14] is the sub-list for method output_type + 6, // [6:10] is the sub-list for method input_type + 6, // [6:6] is the sub-list for extension type_name + 6, // [6:6] is the sub-list for extension extendee + 0, // [0:6] is the sub-list for field type_name } func init() { file_lspd_proto_init() } @@ -957,7 +1096,7 @@ func file_lspd_proto_init() { } } file_lspd_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*OpenChannelRequest); i { + switch v := v.(*OpeningFeeParams); i { case 0: return &v.state case 1: @@ -969,7 +1108,7 @@ func file_lspd_proto_init() { } } file_lspd_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*OpenChannelReply); i { + switch v := v.(*OpenChannelRequest); i { case 0: return &v.state case 1: @@ -981,7 +1120,7 @@ func file_lspd_proto_init() { } } file_lspd_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*RegisterPaymentRequest); i { + switch v := v.(*OpenChannelReply); i { case 0: return &v.state case 1: @@ -993,7 +1132,7 @@ func file_lspd_proto_init() { } } file_lspd_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*RegisterPaymentReply); i { + switch v := v.(*RegisterPaymentRequest); i { case 0: return &v.state case 1: @@ -1005,7 +1144,7 @@ func file_lspd_proto_init() { } } file_lspd_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PaymentInformation); i { + switch v := v.(*RegisterPaymentReply); i { case 0: return &v.state case 1: @@ -1017,7 +1156,7 @@ func file_lspd_proto_init() { } } file_lspd_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Encrypted); i { + switch v := v.(*PaymentInformation); i { case 0: return &v.state case 1: @@ -1029,7 +1168,7 @@ func file_lspd_proto_init() { } } file_lspd_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Signed); i { + switch v := v.(*Encrypted); i { case 0: return &v.state case 1: @@ -1041,7 +1180,7 @@ func file_lspd_proto_init() { } } file_lspd_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*CheckChannelsRequest); i { + switch v := v.(*Signed); i { case 0: return &v.state case 1: @@ -1053,6 +1192,18 @@ func file_lspd_proto_init() { } } file_lspd_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CheckChannelsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_lspd_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*CheckChannelsReply); i { case 0: return &v.state @@ -1071,7 +1222,7 @@ func file_lspd_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_lspd_proto_rawDesc, NumEnums: 0, - NumMessages: 15, + NumMessages: 16, NumExtensions: 0, NumServices: 1, }, diff --git a/rpc/lspd.proto b/rpc/lspd.proto index 45cbc30..4fe164f 100644 --- a/rpc/lspd.proto +++ b/rpc/lspd.proto @@ -47,14 +47,27 @@ message ChannelInformationReply { /// the channel. int64 min_htlc_msat = 9 [ json_name = "min_htlc_msat" ]; - int64 channel_fee_permyriad = 10; + int64 channel_fee_permyriad = 10 [deprecated = true]; bytes lsp_pubkey = 11; // The channel can be closed if not used this duration in seconds. - int64 max_inactive_duration = 12; + int64 max_inactive_duration = 12 [deprecated = true]; - int64 channel_minimum_fee_msat = 13; + int64 channel_minimum_fee_msat = 13 [deprecated = true]; + + repeated OpeningFeeParams opening_fee_params_menu = 14; +} + +message OpeningFeeParams { + uint64 min_msat = 1; + uint32 proportional = 2; + string valid_until = 3; + + // The channel can be closed if not used this duration in blocks. + uint32 max_idle_time = 4; + uint32 max_client_to_self_delay = 5; + string promise = 6; } message OpenChannelRequest { @@ -80,6 +93,7 @@ message PaymentInformation { int64 incoming_amount_msat = 4; int64 outgoing_amount_msat = 5; string tag = 6; + OpeningFeeParams opening_fee_params = 7; } message Encrypted { From a16e87748b877fae3465b154d716ee70bec8b813 Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Thu, 11 May 2023 15:37:28 +0200 Subject: [PATCH 169/214] return opening_fee_params on channel info call --- basetypes/time.go | 3 ++ config/config.go | 16 +++++++ main.go | 3 +- server.go | 114 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 135 insertions(+), 1 deletion(-) create mode 100644 basetypes/time.go diff --git a/basetypes/time.go b/basetypes/time.go new file mode 100644 index 0000000..6c28c37 --- /dev/null +++ b/basetypes/time.go @@ -0,0 +1,3 @@ +package basetypes + +var TIME_FORMAT string = "2006-01-02T15:04:05.999Z" diff --git a/config/config.go b/config/config.go index 4587f85..662f1dc 100644 --- a/config/config.go +++ b/config/config.go @@ -66,6 +66,22 @@ type NodeConfig struct { // The channel can be closed if not used this duration in seconds. MaxInactiveDuration uint64 `json:"maxInactiveDuration,string"` + // The validity duration of an opening params promise. + FeeValidityDuration uint64 `json:"feeValidityDuration,string"` + + // Maximum number of blocks that the client is allowed to set its + // `to_self_delay` parameter. + MaxClientToSelfDelay uint64 `json:"maxClientToSelfDelay,string"` + + // Multiplication factor to calculate the minimum fee for a JIT channel open. + // The resulting fee after multiplying sat/vbyte by the multiplication factor + // is denominated in millisat. + // e.g. if you expect to publish 500 bytes onchain with the given sat/vbyte + // fee rate, and take a margin of 20%, the fee multiplication factor should + // be 500 * 1.2 * 1000 = 600000. With 20 sat/vbyte, the resulting minimum fee + // would be 600000 * 20 = 12000000msat = 12000sat. + FeeMultiplicationFactor uint64 `json:"feeMultiplicationFactor,string"` + // Set this field to connect to an LND node. Lnd *LndConfig `json:"lnd,omitempty"` diff --git a/main.go b/main.go index a2821a5..c82a25a 100644 --- a/main.go +++ b/main.go @@ -117,7 +117,8 @@ func main() { address := os.Getenv("LISTEN_ADDRESS") certMagicDomain := os.Getenv("CERTMAGIC_DOMAIN") - s, err := NewGrpcServer(nodes, address, certMagicDomain, interceptStore) + cachedEstimator := chain.NewCachedFeeEstimator(feeEstimator) + s, err := NewGrpcServer(nodes, address, certMagicDomain, interceptStore, feeStrategy, cachedEstimator) if err != nil { log.Fatalf("failed to initialize grpc server: %v", err) } diff --git a/server.go b/server.go index ed7e528..d3517e4 100644 --- a/server.go +++ b/server.go @@ -2,15 +2,20 @@ package main import ( "context" + "crypto/sha256" "crypto/tls" "encoding/hex" "encoding/json" "fmt" "log" + "math" "net" "strings" + "time" + "github.com/breez/lspd/basetypes" "github.com/breez/lspd/btceclegacy" + "github.com/breez/lspd/chain" "github.com/breez/lspd/cln" "github.com/breez/lspd/config" "github.com/breez/lspd/interceptor" @@ -27,12 +32,15 @@ import ( "google.golang.org/grpc/status" "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/ecdsa" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" "github.com/caddyserver/certmagic" "github.com/lightningnetwork/lnd/lnwire" ) +var TIME_FORMAT string = "2006-01-02T15:04:05.999Z" + type server struct { lspdrpc.ChannelOpenerServer address string @@ -41,6 +49,8 @@ type server struct { s *grpc.Server nodes map[string]*node store interceptor.InterceptStore + feeStrategy chain.FeeStrategy + feeEstimator chain.FeeEstimator } type node struct { @@ -59,6 +69,11 @@ func (s *server) ChannelInformation(ctx context.Context, in *lspdrpc.ChannelInfo return nil, err } + params, err := s.createOpeningParams(ctx, node) + if err != nil { + return nil, err + } + return &lspdrpc.ChannelInformationReply{ Name: node.nodeConfig.Name, Pubkey: node.nodeConfig.NodePubkey, @@ -73,9 +88,104 @@ func (s *server) ChannelInformation(ctx context.Context, in *lspdrpc.ChannelInfo 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: []*lspdrpc.OpeningFeeParams{params}, }, nil } +func (s *server) createOpeningParams( + ctx context.Context, + node *node, +) (*lspdrpc.OpeningFeeParams, error) { + // Get a fee estimate. + estimate, err := s.feeEstimator.EstimateFeeRate(ctx, s.feeStrategy) + if err != nil { + log.Printf("Failed to get fee estimate: %v", err) + return nil, fmt.Errorf("failed to get fee estimate") + } + + // Multiply the fee estiimate by the configured multiplication factor. + minFeeMsat := estimate.SatPerVByte * + float64(node.nodeConfig.FeeMultiplicationFactor) + + // Make sure the fee is not lower than the minimum fee. + minFeeMsat = math.Max(minFeeMsat, float64(node.nodeConfig.ChannelMinimumFeeMsat)) + + validUntil := time.Now().UTC().Add( + time.Second * time.Duration(node.nodeConfig.FeeValidityDuration), + ) + params := &lspdrpc.OpeningFeeParams{ + MinMsat: uint64(minFeeMsat), + // Proportional is ppm, so divide by 100. + Proportional: uint32(node.nodeConfig.ChannelFeePermyriad / 100), + ValidUntil: validUntil.Format(basetypes.TIME_FORMAT), + // MaxInactiveDuration is in seconds, so divide by 600 for blocks. + MaxIdleTime: uint32(node.nodeConfig.MaxInactiveDuration / 600), + MaxClientToSelfDelay: uint32(node.nodeConfig.MaxClientToSelfDelay), + } + + promise, err := createPromise(node, params) + if err != nil { + log.Printf("Failed to create promise: %v", err) + } + + params.Promise = *promise + return params, nil +} + +func createPromise(node *node, params *lspdrpc.OpeningFeeParams) (*string, error) { + + // First hash all the values in the params in a fixed order. + items := []interface{}{ + params.MinMsat, + params.Proportional, + params.ValidUntil, + params.MaxIdleTime, + params.MaxClientToSelfDelay, + } + blob, err := json.Marshal(items) + if err != nil { + return nil, err + } + hash := sha256.Sum256(blob) + + // Sign the hash with the private key of the LSP id. + sig, err := ecdsa.SignCompact(node.privateKey, hash[:], true) + if err != nil { + return nil, err + } + + // The promise is the hex encoded hash of the signature. + result := sha256.Sum256(sig) + promise := hex.EncodeToString(result[:]) + return &promise, nil +} + +func validateOpeningFeeParams(node *node, params *lspdrpc.OpeningFeeParams) bool { + if params == nil { + return false + } + + promise, err := createPromise(node, params) + if err != nil { + return false + } + + if *promise != params.Promise { + return false + } + + t, err := time.Parse(basetypes.TIME_FORMAT, params.ValidUntil) + if err != nil { + return false + } + + if time.Now().UTC().After(t) { + return false + } + + return true +} + func (s *server) RegisterPayment(ctx context.Context, in *lspdrpc.RegisterPaymentRequest) (*lspdrpc.RegisterPaymentReply, error) { node, err := getNode(ctx) if err != nil { @@ -270,6 +380,8 @@ func NewGrpcServer( address string, certmagicDomain string, store interceptor.InterceptStore, + feeStrategy chain.FeeStrategy, + feeEstimator chain.FeeEstimator, ) (*server, error) { if len(configs) == 0 { return nil, fmt.Errorf("no nodes supplied") @@ -329,6 +441,8 @@ func NewGrpcServer( certmagicDomain: certmagicDomain, nodes: nodes, store: store, + feeStrategy: feeStrategy, + feeEstimator: feeEstimator, }, nil } From d76b79e0d706965efc4a4210140d1330aecf9321 Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Thu, 11 May 2023 13:29:08 +0200 Subject: [PATCH 170/214] use opening_fee_params on register payment . --- interceptor/store.go | 11 ++++- postgresql/intercept_store.go | 25 +++++++---- .../000010_opening_fee_params.down.sql | 1 + .../000010_opening_fee_params.up.sql | 1 + server.go | 45 +++++++++++++++---- 5 files changed, 65 insertions(+), 18 deletions(-) create mode 100644 postgresql/migrations/000010_opening_fee_params.down.sql create mode 100644 postgresql/migrations/000010_opening_fee_params.up.sql diff --git a/interceptor/store.go b/interceptor/store.go index 3956ec2..a4fd774 100644 --- a/interceptor/store.go +++ b/interceptor/store.go @@ -6,9 +6,18 @@ import ( "github.com/btcsuite/btcd/wire" ) +type OpeningFeeParams struct { + MinMsat uint64 `json:"min_msat,string"` + Proportional uint32 `json:"proportional"` + ValidUntil string `json:"valid_until"` + MaxIdleTime uint32 `json:"max_idle_time"` + MaxClientToSelfDelay uint32 `json:"max_client_to_self_delay"` + Promise string `json:"promise"` +} + type InterceptStore interface { PaymentInfo(htlcPaymentHash []byte) ([]byte, []byte, []byte, int64, int64, *wire.OutPoint, error) SetFundingTx(paymentHash []byte, channelPoint *wire.OutPoint) error - RegisterPayment(destination, paymentHash, paymentSecret []byte, incomingAmountMsat, outgoingAmountMsat int64, tag string) error + RegisterPayment(params *OpeningFeeParams, destination, paymentHash, paymentSecret []byte, incomingAmountMsat, outgoingAmountMsat int64, tag string) error InsertChannel(initialChanID, confirmedChanId uint64, channelPoint string, nodeID []byte, lastUpdate time.Time) error } diff --git a/postgresql/intercept_store.go b/postgresql/intercept_store.go index c3dec86..1796faa 100644 --- a/postgresql/intercept_store.go +++ b/postgresql/intercept_store.go @@ -2,11 +2,13 @@ package postgresql import ( "context" + "encoding/json" "fmt" "log" "time" "github.com/breez/lspd/basetypes" + "github.com/breez/lspd/interceptor" "github.com/btcsuite/btcd/wire" "github.com/jackc/pgtype" "github.com/jackc/pgx/v4" @@ -60,22 +62,29 @@ func (s *PostgresInterceptStore) SetFundingTx(paymentHash []byte, channelPoint * return err } -func (s *PostgresInterceptStore) RegisterPayment(destination, paymentHash, paymentSecret []byte, incomingAmountMsat, outgoingAmountMsat int64, tag string) error { +func (s *PostgresInterceptStore) RegisterPayment(params *interceptor.OpeningFeeParams, destination, paymentHash, paymentSecret []byte, incomingAmountMsat, outgoingAmountMsat int64, tag string) error { var t *string if tag != "" { t = &tag } + + p, err := json.Marshal(params) + if err != nil { + log.Printf("Failed to marshal OpeningFeeParams: %v", err) + return err + } + commandTag, err := s.pool.Exec(context.Background(), `INSERT INTO - payments (destination, payment_hash, payment_secret, incoming_amount_msat, outgoing_amount_msat, tag) - VALUES ($1, $2, $3, $4, $5, $6) + payments (destination, payment_hash, payment_secret, incoming_amount_msat, outgoing_amount_msat, tag, opening_fee_params) + VALUES ($1, $2, $3, $4, $5, $6, $7) ON CONFLICT DO NOTHING`, - destination, paymentHash, paymentSecret, incomingAmountMsat, outgoingAmountMsat, t) - log.Printf("registerPayment(%x, %x, %x, %v, %v, %v) rows: %v err: %v", - destination, paymentHash, paymentSecret, incomingAmountMsat, outgoingAmountMsat, tag, commandTag.RowsAffected(), err) + destination, paymentHash, paymentSecret, incomingAmountMsat, outgoingAmountMsat, t, p) + log.Printf("registerPayment(%x, %x, %x, %v, %v, %v, %s) rows: %v err: %v", + destination, paymentHash, paymentSecret, incomingAmountMsat, outgoingAmountMsat, tag, p, commandTag.RowsAffected(), err) if err != nil { - return fmt.Errorf("registerPayment(%x, %x, %x, %v, %v, %v) error: %w", - destination, paymentHash, paymentSecret, incomingAmountMsat, outgoingAmountMsat, tag, err) + return fmt.Errorf("registerPayment(%x, %x, %x, %v, %v, %v, %s) error: %w", + destination, paymentHash, paymentSecret, incomingAmountMsat, outgoingAmountMsat, tag, p, err) } return nil } diff --git a/postgresql/migrations/000010_opening_fee_params.down.sql b/postgresql/migrations/000010_opening_fee_params.down.sql new file mode 100644 index 0000000..20173eb --- /dev/null +++ b/postgresql/migrations/000010_opening_fee_params.down.sql @@ -0,0 +1 @@ +ALTER TABLE public.payments DROP COLUMN opening_fee_params; diff --git a/postgresql/migrations/000010_opening_fee_params.up.sql b/postgresql/migrations/000010_opening_fee_params.up.sql new file mode 100644 index 0000000..0efe06c --- /dev/null +++ b/postgresql/migrations/000010_opening_fee_params.up.sql @@ -0,0 +1 @@ +ALTER TABLE public.payments ADD opening_fee_params jsonb NULL; diff --git a/server.go b/server.go index d3517e4..361944c 100644 --- a/server.go +++ b/server.go @@ -39,8 +39,6 @@ import ( "github.com/lightningnetwork/lnd/lnwire" ) -var TIME_FORMAT string = "2006-01-02T15:04:05.999Z" - type server struct { lspdrpc.ChannelOpenerServer address string @@ -186,7 +184,10 @@ func validateOpeningFeeParams(node *node, params *lspdrpc.OpeningFeeParams) bool return true } -func (s *server) RegisterPayment(ctx context.Context, in *lspdrpc.RegisterPaymentRequest) (*lspdrpc.RegisterPaymentReply, error) { +func (s *server) RegisterPayment( + ctx context.Context, + in *lspdrpc.RegisterPaymentRequest, +) (*lspdrpc.RegisterPaymentReply, error) { node, err := getNode(ctx) if err != nil { return nil, err @@ -223,12 +224,38 @@ func (s *server) RegisterPayment(ctx context.Context, in *lspdrpc.RegisterPaymen } } - err = checkPayment(node.nodeConfig, pi.IncomingAmountMsat, pi.OutgoingAmountMsat) + // TODO: Remove this nil check and the else cluase when we enforce all + // clients to use opening_fee_params. + if pi.OpeningFeeParams != nil { + valid := validateOpeningFeeParams(node, pi.OpeningFeeParams) + if !valid { + return nil, fmt.Errorf("invalid opening_fee_params") + } + } else { + log.Printf("DEPRECATED: RegisterPayment with deprecated fee mechanism.") + pi.OpeningFeeParams = &lspdrpc.OpeningFeeParams{ + 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), + MaxClientToSelfDelay: uint32(node.nodeConfig.MaxClientToSelfDelay), + } + } + + err = checkPayment(pi.OpeningFeeParams, 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 = s.store.RegisterPayment(pi.Destination, pi.PaymentHash, pi.PaymentSecret, pi.IncomingAmountMsat, pi.OutgoingAmountMsat, pi.Tag) + params := &interceptor.OpeningFeeParams{ + MinMsat: pi.OpeningFeeParams.MinMsat, + Proportional: pi.OpeningFeeParams.Proportional, + ValidUntil: pi.OpeningFeeParams.ValidUntil, + MaxIdleTime: pi.OpeningFeeParams.MaxIdleTime, + MaxClientToSelfDelay: pi.OpeningFeeParams.MaxClientToSelfDelay, + Promise: pi.OpeningFeeParams.Promise, + } + err = s.store.RegisterPayment(params, pi.Destination, pi.PaymentHash, pi.PaymentSecret, pi.IncomingAmountMsat, pi.OutgoingAmountMsat, pi.Tag) if err != nil { log.Printf("RegisterPayment() error: %v", err) return nil, fmt.Errorf("RegisterPayment() error: %w", err) @@ -534,10 +561,10 @@ func getNode(ctx context.Context) (*node, error) { return node, nil } -func checkPayment(config *config.NodeConfig, incomingAmountMsat, outgoingAmountMsat int64) error { - fees := incomingAmountMsat * config.ChannelFeePermyriad / 10_000 / 1_000 * 1_000 - if fees < config.ChannelMinimumFeeMsat { - fees = config.ChannelMinimumFeeMsat +func checkPayment(params *lspdrpc.OpeningFeeParams, incomingAmountMsat, outgoingAmountMsat int64) error { + fees := incomingAmountMsat * int64(params.Proportional) / 1_000_000 / 1_000 * 1_000 + if fees < int64(params.MinMsat) { + fees = int64(params.MinMsat) } if incomingAmountMsat-outgoingAmountMsat < fees { return fmt.Errorf("not enough fees") From 4379dee7c8bcc40b57d7363d748484068e875721 Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Thu, 11 May 2023 15:26:27 +0200 Subject: [PATCH 171/214] verify validity before open JIT channel . --- interceptor/intercept.go | 32 +++++++++++++++++++++++++++++++- interceptor/store.go | 2 +- postgresql/intercept_store.go | 20 +++++++++++++++----- 3 files changed, 47 insertions(+), 7 deletions(-) diff --git a/interceptor/intercept.go b/interceptor/intercept.go index 7d8e1cb..df5678c 100644 --- a/interceptor/intercept.go +++ b/interceptor/intercept.go @@ -9,6 +9,7 @@ import ( "math/big" "time" + "github.com/breez/lspd/basetypes" "github.com/breez/lspd/chain" "github.com/breez/lspd/config" "github.com/breez/lspd/lightning" @@ -75,7 +76,7 @@ func NewInterceptor( func (i *Interceptor) Intercept(nextHop string, reqPaymentHash []byte, reqOutgoingAmountMsat uint64, reqOutgoingExpiry uint32, reqIncomingExpiry uint32) InterceptResult { reqPaymentHashStr := hex.EncodeToString(reqPaymentHash) resp, _, _ := i.payHashGroup.Do(reqPaymentHashStr, func() (interface{}, error) { - paymentHash, paymentSecret, destination, incomingAmountMsat, outgoingAmountMsat, channelPoint, err := i.store.PaymentInfo(reqPaymentHash) + params, paymentHash, paymentSecret, destination, incomingAmountMsat, outgoingAmountMsat, channelPoint, err := i.store.PaymentInfo(reqPaymentHash) if err != nil { log.Printf("paymentInfo(%x) error: %v", reqPaymentHash, err) return InterceptResult{ @@ -93,6 +94,18 @@ func (i *Interceptor) Intercept(nextHop string, reqPaymentHash []byte, reqOutgoi if channelPoint == nil { if bytes.Equal(paymentHash, reqPaymentHash) { + // TODO: When opening_fee_params is enforced, turn this check in a temporary channel failure. + if params == nil { + log.Printf("DEPRECATED: Intercepted htlc with deprecated fee mechanism.") + params = &OpeningFeeParams{ + MinMsat: uint64(i.config.ChannelMinimumFeeMsat), + Proportional: uint32(i.config.ChannelFeePermyriad * 100), + ValidUntil: time.Now().UTC().Add(time.Duration(time.Hour * 24)).Format(basetypes.TIME_FORMAT), + MaxIdleTime: uint32(i.config.MaxInactiveDuration / 600), + MaxClientToSelfDelay: uint32(i.config.MaxClientToSelfDelay), + } + } + if int64(reqIncomingExpiry)-int64(reqOutgoingExpiry) < int64(i.config.TimeLockDelta) { return InterceptResult{ Action: INTERCEPT_FAIL_HTLC_WITH_CODE, @@ -100,6 +113,23 @@ func (i *Interceptor) Intercept(nextHop string, reqPaymentHash []byte, reqOutgoi }, nil } + validUntil, err := time.Parse(basetypes.TIME_FORMAT, params.ValidUntil) + if err != nil { + log.Printf("time.Parse(%s, %s) failed. Failing channel open: %v", basetypes.TIME_FORMAT, params.ValidUntil, err) + return InterceptResult{ + Action: INTERCEPT_FAIL_HTLC_WITH_CODE, + FailureCode: FAILURE_TEMPORARY_CHANNEL_FAILURE, + }, nil + } + + if time.Now().UTC().After(validUntil) { + log.Printf("Intercepted expired payment registration. Failing payment. payment hash: %x, valid until: %s", paymentHash, params.ValidUntil) + return InterceptResult{ + Action: INTERCEPT_FAIL_HTLC_WITH_CODE, + FailureCode: FAILURE_TEMPORARY_CHANNEL_FAILURE, + }, nil + } + channelPoint, err = i.openChannel(reqPaymentHash, destination, incomingAmountMsat) if err != nil { log.Printf("openChannel(%x, %v) err: %v", destination, incomingAmountMsat, err) diff --git a/interceptor/store.go b/interceptor/store.go index a4fd774..eebb2f3 100644 --- a/interceptor/store.go +++ b/interceptor/store.go @@ -16,7 +16,7 @@ type OpeningFeeParams struct { } type InterceptStore interface { - PaymentInfo(htlcPaymentHash []byte) ([]byte, []byte, []byte, int64, int64, *wire.OutPoint, error) + PaymentInfo(htlcPaymentHash []byte) (*OpeningFeeParams, []byte, []byte, []byte, int64, int64, *wire.OutPoint, error) SetFundingTx(paymentHash []byte, channelPoint *wire.OutPoint) error RegisterPayment(params *OpeningFeeParams, destination, paymentHash, paymentSecret []byte, incomingAmountMsat, outgoingAmountMsat int64, tag string) error InsertChannel(initialChanID, confirmedChanId uint64, channelPoint string, nodeID []byte, lastUpdate time.Time) error diff --git a/postgresql/intercept_store.go b/postgresql/intercept_store.go index 1796faa..1aef836 100644 --- a/postgresql/intercept_store.go +++ b/postgresql/intercept_store.go @@ -23,23 +23,24 @@ func NewPostgresInterceptStore(pool *pgxpool.Pool) *PostgresInterceptStore { return &PostgresInterceptStore{pool: pool} } -func (s *PostgresInterceptStore) PaymentInfo(htlcPaymentHash []byte) ([]byte, []byte, []byte, int64, int64, *wire.OutPoint, error) { +func (s *PostgresInterceptStore) PaymentInfo(htlcPaymentHash []byte) (*interceptor.OpeningFeeParams, []byte, []byte, []byte, int64, int64, *wire.OutPoint, error) { var ( + p *string paymentHash, paymentSecret, destination []byte incomingAmountMsat, outgoingAmountMsat int64 fundingTxID []byte fundingTxOutnum pgtype.Int4 ) err := s.pool.QueryRow(context.Background(), - `SELECT payment_hash, payment_secret, destination, incoming_amount_msat, outgoing_amount_msat, funding_tx_id, funding_tx_outnum + `SELECT payment_hash, payment_secret, destination, incoming_amount_msat, outgoing_amount_msat, funding_tx_id, funding_tx_outnum, opening_fee_params FROM payments WHERE payment_hash=$1 OR sha256('probing-01:' || payment_hash)=$1`, - htlcPaymentHash).Scan(&paymentHash, &paymentSecret, &destination, &incomingAmountMsat, &outgoingAmountMsat, &fundingTxID, &fundingTxOutnum) + htlcPaymentHash).Scan(&paymentHash, &paymentSecret, &destination, &incomingAmountMsat, &outgoingAmountMsat, &fundingTxID, &fundingTxOutnum, &p) if err != nil { if err == pgx.ErrNoRows { err = nil } - return nil, nil, nil, 0, 0, nil, err + return nil, nil, nil, nil, 0, 0, nil, err } var cp *wire.OutPoint @@ -49,7 +50,16 @@ func (s *PostgresInterceptStore) PaymentInfo(htlcPaymentHash []byte) ([]byte, [] log.Printf("invalid funding txid in database %x", fundingTxID) } } - return paymentHash, paymentSecret, destination, incomingAmountMsat, outgoingAmountMsat, cp, nil + + var params *interceptor.OpeningFeeParams + if p != nil { + err = json.Unmarshal([]byte(*p), ¶ms) + if err != nil { + log.Printf("Failed to unmarshal OpeningFeeParams '%s': %v", *p, err) + return nil, nil, nil, nil, 0, 0, nil, err + } + } + return params, paymentHash, paymentSecret, destination, incomingAmountMsat, outgoingAmountMsat, cp, nil } func (s *PostgresInterceptStore) SetFundingTx(paymentHash []byte, channelPoint *wire.OutPoint) error { From d0de038d45f91dd1b88ae47daf5133b0a22a3853 Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Thu, 11 May 2023 16:50:41 +0200 Subject: [PATCH 172/214] add new config to integration tests --- itest/lspd_node.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/itest/lspd_node.go b/itest/lspd_node.go index bfeaf10..c5f5aa5 100644 --- a/itest/lspd_node.go +++ b/itest/lspd_node.go @@ -109,6 +109,9 @@ func newLspd(h *lntest.TestHarness, name string, nodeConfig *config.NodeConfig, ChannelMinimumFeeMsat: 2000000, AdditionalChannelCapacity: 100000, MaxInactiveDuration: 3888000, + FeeValidityDuration: 60 * 60 * 24, + MaxClientToSelfDelay: 2016, + FeeMultiplicationFactor: 1000000, Lnd: lnd, Cln: cln, } From cad6540f4289fb6b22db3ada7304b2b404dab652 Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Fri, 12 May 2023 09:22:11 +0200 Subject: [PATCH 173/214] always use mempool api --- itest/lspd_node.go | 1 - main.go | 52 +++++++++++++++++++++++----------------------- sample.env | 6 ++---- 3 files changed, 28 insertions(+), 31 deletions(-) diff --git a/itest/lspd_node.go b/itest/lspd_node.go index c5f5aa5..e4f01b3 100644 --- a/itest/lspd_node.go +++ b/itest/lspd_node.go @@ -129,7 +129,6 @@ func newLspd(h *lntest.TestHarness, name string, nodeConfig *config.NodeConfig, nodes, fmt.Sprintf("DATABASE_URL=%s", postgresBackend.ConnectionString()), fmt.Sprintf("LISTEN_ADDRESS=%s", grpcAddress), - "USE_MEMPOOL_FEE_ESTIMATION=true", "MEMPOOL_API_BASE_URL=https://mempool.space/api/v1/", "MEMPOOL_PRIORITY=economy", } diff --git a/main.go b/main.go index c82a25a..784622c 100644 --- a/main.go +++ b/main.go @@ -41,34 +41,34 @@ func main() { log.Fatalf("need at least one node configured in NODES.") } - var feeEstimator chain.FeeEstimator - var feeStrategy chain.FeeStrategy - useMempool := os.Getenv("USE_MEMPOOL_FEE_ESTIMATION") == "true" - if useMempool { - mempoolUrl := os.Getenv("MEMPOOL_API_BASE_URL") - feeEstimator, err = mempool.NewMempoolClient(mempoolUrl) - if err != nil { - log.Fatalf("failed to initialize mempool client: %v", err) - } - - envFeeStrategy := os.Getenv("MEMPOOL_PRIORITY") - switch strings.ToLower(envFeeStrategy) { - case "minimum": - feeStrategy = chain.FeeStrategyMinimum - case "economy": - feeStrategy = chain.FeeStrategyEconomy - case "hour": - feeStrategy = chain.FeeStrategyHour - case "halfhour": - feeStrategy = chain.FeeStrategyHalfHour - case "fastest": - feeStrategy = chain.FeeStrategyFastest - default: - feeStrategy = chain.FeeStrategyEconomy - } - log.Printf("using mempool api for fee estimation: %v, fee strategy: %v:%v", mempoolUrl, envFeeStrategy, feeStrategy) + mempoolUrl := os.Getenv("MEMPOOL_API_BASE_URL") + if mempoolUrl == "" { + log.Fatalf("No mempool url configured.") } + feeEstimator, err := mempool.NewMempoolClient(mempoolUrl) + if err != nil { + log.Fatalf("failed to initialize mempool client: %v", err) + } + + var feeStrategy chain.FeeStrategy + envFeeStrategy := os.Getenv("MEMPOOL_PRIORITY") + switch strings.ToLower(envFeeStrategy) { + case "minimum": + feeStrategy = chain.FeeStrategyMinimum + case "economy": + feeStrategy = chain.FeeStrategyEconomy + case "hour": + feeStrategy = chain.FeeStrategyHour + case "halfhour": + feeStrategy = chain.FeeStrategyHalfHour + case "fastest": + feeStrategy = chain.FeeStrategyFastest + default: + feeStrategy = chain.FeeStrategyEconomy + } + log.Printf("using mempool api for fee estimation: %v, fee strategy: %v:%v", mempoolUrl, envFeeStrategy, feeStrategy) + databaseUrl := os.Getenv("DATABASE_URL") pool, err := postgresql.PgConnect(databaseUrl) if err != nil { diff --git a/sample.env b/sample.env index f266c14..b595154 100644 --- a/sample.env +++ b/sample.env @@ -30,10 +30,8 @@ CHANNELMISMATCH_NOTIFICATION_TO='["Name1 "]' CHANNELMISMATCH_NOTIFICATION_CC='["Name2 ","Name3 "]' CHANNELMISMATCH_NOTIFICATION_FROM="Name4 " -# By default lspd uses the fee estimation from the lightning node it is connected -# to for opening new channels. You can use mempool fee estimation instead by -# setting below variables. -USE_MEMPOOL_FEE_ESTIMATION=true +# lspd uses the fee estimation from mempool.space for opening new channels. +# Change below setting for you own mempool instance. MEMPOOL_API_BASE_URL=https://mempool.space/api/v1/ # Priority to use for opening channels when using the mempool api. From aafb07e5f0417263f16aa17396d7883d3e0465a9 Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Fri, 12 May 2023 10:54:21 +0200 Subject: [PATCH 174/214] Use local mempool api in tests --- itest/cln_lspd_node.go | 4 +- itest/config_test.go | 5 ++- itest/lnd_lspd_node.go | 4 +- itest/lspd_node.go | 4 +- itest/lspd_test.go | 16 ++++--- itest/mempool_api.go | 85 ++++++++++++++++++++++++++++++++++++ itest/test_params.go | 7 ++- itest/zero_conf_utxo_test.go | 2 +- 8 files changed, 113 insertions(+), 14 deletions(-) create mode 100644 itest/mempool_api.go diff --git a/itest/cln_lspd_node.go b/itest/cln_lspd_node.go index 1697e26..30a9f7e 100644 --- a/itest/cln_lspd_node.go +++ b/itest/cln_lspd_node.go @@ -42,7 +42,7 @@ type clnLspNodeRuntime struct { cleanups []*lntest.Cleanup } -func NewClnLspdNode(h *lntest.TestHarness, m *lntest.Miner, name string, nodeConfig *config.NodeConfig) LspNode { +func NewClnLspdNode(h *lntest.TestHarness, m *lntest.Miner, mem *mempoolApi, name string, nodeConfig *config.NodeConfig) LspNode { scriptDir := h.GetDirectory("lspd") pluginBinary := *clnPluginExec pluginPort, err := lntest.GetPort() @@ -65,7 +65,7 @@ func NewClnLspdNode(h *lntest.TestHarness, m *lntest.Miner, name string, nodeCon PluginAddress: pluginAddress, SocketPath: filepath.Join(lightningNode.SocketDir(), lightningNode.SocketFile()), } - lspbase, err := newLspd(h, name, nodeConfig, nil, cln) + lspbase, err := newLspd(h, mem, name, nodeConfig, nil, cln) if err != nil { h.T.Fatalf("failed to initialize lspd") } diff --git a/itest/config_test.go b/itest/config_test.go index 760b89d..cf69ea8 100644 --- a/itest/config_test.go +++ b/itest/config_test.go @@ -19,7 +19,10 @@ func TestConfigParameters(t *testing.T) { m := lntest.NewMiner(h) m.Start() - lsp := NewClnLspdNode(h, m, "lsp", nil) + mem := NewMempoolApi(h) + mem.Start() + + lsp := NewClnLspdNode(h, m, mem, "lsp", nil) lsp.Start() log.Printf("Waiting %v to allow lsp server to activate.", htlcInterceptorDelay) diff --git a/itest/lnd_lspd_node.go b/itest/lnd_lspd_node.go index 9f7e0ce..4864815 100644 --- a/itest/lnd_lspd_node.go +++ b/itest/lnd_lspd_node.go @@ -38,7 +38,7 @@ type lndLspNodeRuntime struct { cleanups []*lntest.Cleanup } -func NewLndLspdNode(h *lntest.TestHarness, m *lntest.Miner, name string, nodeConfig *config.NodeConfig) LspNode { +func NewLndLspdNode(h *lntest.TestHarness, m *lntest.Miner, mem *mempoolApi, name string, nodeConfig *config.NodeConfig) LspNode { args := []string{ "--protocol.zero-conf", "--protocol.option-scid-alias", @@ -56,7 +56,7 @@ func NewLndLspdNode(h *lntest.TestHarness, m *lntest.Miner, name string, nodeCon Cert: string(lightningNode.TlsCert()), Macaroon: hex.EncodeToString(lightningNode.Macaroon()), } - lspBase, err := newLspd(h, name, nodeConfig, lnd, nil) + lspBase, err := newLspd(h, mem, name, nodeConfig, lnd, nil) if err != nil { h.T.Fatalf("failed to initialize lspd") } diff --git a/itest/lspd_node.go b/itest/lspd_node.go index e4f01b3..a4100c1 100644 --- a/itest/lspd_node.go +++ b/itest/lspd_node.go @@ -60,7 +60,7 @@ type lspBase struct { postgresBackend *PostgresContainer } -func newLspd(h *lntest.TestHarness, name string, nodeConfig *config.NodeConfig, lnd *config.LndConfig, cln *config.ClnConfig, envExt ...string) (*lspBase, error) { +func newLspd(h *lntest.TestHarness, mem *mempoolApi, name string, nodeConfig *config.NodeConfig, lnd *config.LndConfig, cln *config.ClnConfig, envExt ...string) (*lspBase, error) { scriptDir := h.GetDirectory(fmt.Sprintf("lspd-%s", name)) log.Printf("%s: Creating LSPD in dir %s", name, scriptDir) @@ -129,7 +129,7 @@ func newLspd(h *lntest.TestHarness, name string, nodeConfig *config.NodeConfig, nodes, fmt.Sprintf("DATABASE_URL=%s", postgresBackend.ConnectionString()), fmt.Sprintf("LISTEN_ADDRESS=%s", grpcAddress), - "MEMPOOL_API_BASE_URL=https://mempool.space/api/v1/", + fmt.Sprintf("MEMPOOL_API_BASE_URL=%s", mem.Address()), "MEMPOOL_PRIORITY=economy", } diff --git a/itest/lspd_test.go b/itest/lspd_test.go index 8c6c73b..e6dd3ff 100644 --- a/itest/lspd_test.go +++ b/itest/lspd_test.go @@ -18,12 +18,12 @@ func TestLspd(t *testing.T) { runTests(t, testCases, "CLN-lspd", clnLspFunc, clnClientFunc) } -func lndLspFunc(h *lntest.TestHarness, m *lntest.Miner, c *config.NodeConfig) LspNode { - return NewLndLspdNode(h, m, "lsp", c) +func lndLspFunc(h *lntest.TestHarness, m *lntest.Miner, mem *mempoolApi, c *config.NodeConfig) LspNode { + return NewLndLspdNode(h, m, mem, "lsp", c) } -func clnLspFunc(h *lntest.TestHarness, m *lntest.Miner, c *config.NodeConfig) LspNode { - return NewClnLspdNode(h, m, "lsp", c) +func clnLspFunc(h *lntest.TestHarness, m *lntest.Miner, mem *mempoolApi, c *config.NodeConfig) LspNode { + return NewClnLspdNode(h, m, mem, "lsp", c) } func lndClientFunc(h *lntest.TestHarness, m *lntest.Miner) BreezClient { @@ -72,10 +72,15 @@ func runTest( log.Printf("Creating miner") miner := lntest.NewMiner(h) miner.Start() + + log.Printf("Creating mempool api") + mem := NewMempoolApi(h) + mem.Start() + log.Printf("Creating lsp") var lsp LspNode if !testCase.skipCreateLsp { - lsp = lspFunc(h, miner, nil) + lsp = lspFunc(h, miner, mem, nil) lsp.Start() } c := clientFunc(h, miner) @@ -85,6 +90,7 @@ func runTest( t: t, h: h, m: miner, + mem: mem, c: c, lsp: lsp, lspFunc: lspFunc, diff --git a/itest/mempool_api.go b/itest/mempool_api.go new file mode 100644 index 0000000..44a5676 --- /dev/null +++ b/itest/mempool_api.go @@ -0,0 +1,85 @@ +package itest + +import ( + "encoding/json" + "fmt" + "net" + "net/http" + + "github.com/breez/lntest" +) + +type RecommendedFeesResponse struct { + FastestFee float64 `json:"fastestFee"` + HalfHourFee float64 `json:"halfHourFee"` + HourFee float64 `json:"hourFee"` + EconomyFee float64 `json:"economyFee"` + MinimumFee float64 `json:"minimumFee"` +} + +type mempoolApi struct { + addr string + h *lntest.TestHarness + fees *RecommendedFeesResponse + lis net.Listener +} + +func NewMempoolApi(h *lntest.TestHarness) *mempoolApi { + port, err := lntest.GetPort() + if err != nil { + h.T.Fatalf("Failed to get port for mempool api: %v", err) + } + + return &mempoolApi{ + addr: fmt.Sprintf("127.0.0.1:%d", port), + h: h, + fees: &RecommendedFeesResponse{ + MinimumFee: 1, + EconomyFee: 1, + HourFee: 1, + HalfHourFee: 1, + FastestFee: 1, + }, + } +} + +func (m *mempoolApi) Address() string { + return fmt.Sprintf("http://%s/api/v1/", m.addr) +} + +func (m *mempoolApi) SetFees(fees *RecommendedFeesResponse) { + m.fees = fees +} + +func (m *mempoolApi) Start() { + mux := http.NewServeMux() + mux.HandleFunc("/api/v1/fees/recommended", func(w http.ResponseWriter, r *http.Request) { + j, err := json.Marshal(m.fees) + if err != nil { + m.h.T.Fatalf("Failed to marshal mempool fees: %v", err) + } + _, err = w.Write(j) + if err != nil { + m.h.T.Fatalf("Failed to write mempool response: %v", err) + } + }) + lis, err := net.Listen("tcp", m.addr) + if err != nil { + m.h.T.Fatalf("failed to start mempool api: %v", err) + } + + m.lis = lis + m.h.AddStoppable(m) + + go http.Serve(lis, mux) +} + +func (m *mempoolApi) Stop() error { + lis := m.lis + if lis == nil { + return nil + } + + m.lis = nil + return lis.Close() +} diff --git a/itest/test_params.go b/itest/test_params.go index a3d420d..ab9b416 100644 --- a/itest/test_params.go +++ b/itest/test_params.go @@ -7,13 +7,14 @@ import ( "github.com/breez/lspd/config" ) -type LspFunc func(h *lntest.TestHarness, m *lntest.Miner, c *config.NodeConfig) LspNode +type LspFunc func(h *lntest.TestHarness, m *lntest.Miner, mem *mempoolApi, c *config.NodeConfig) LspNode type ClientFunc func(h *lntest.TestHarness, m *lntest.Miner) BreezClient type testParams struct { t *testing.T h *lntest.TestHarness m *lntest.Miner + mem *mempoolApi c BreezClient lsp LspNode lspFunc LspFunc @@ -28,6 +29,10 @@ func (h *testParams) Miner() *lntest.Miner { return h.m } +func (h *testParams) Mempool() *mempoolApi { + return h.mem +} + func (h *testParams) Lsp() LspNode { return h.lsp } diff --git a/itest/zero_conf_utxo_test.go b/itest/zero_conf_utxo_test.go index ea991fc..44ab3d1 100644 --- a/itest/zero_conf_utxo_test.go +++ b/itest/zero_conf_utxo_test.go @@ -16,7 +16,7 @@ func testOpenZeroConfUtxo(p *testParams) { alice.Fund(10000000) minConfs := uint32(0) - lsp := p.lspFunc(p.h, p.m, &config.NodeConfig{MinConfs: &minConfs}) + lsp := p.lspFunc(p.h, p.m, p.mem, &config.NodeConfig{MinConfs: &minConfs}) lsp.Start() log.Print("Opening channel between Alice and the lsp") From acffe289ff04183764ccd70ed2e0bce447d9dfcc Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Fri, 12 May 2023 13:16:58 +0200 Subject: [PATCH 175/214] add a test for dynamic fees --- itest/bob_offline_test.go | 4 +- itest/cltv_test.go | 4 +- itest/dynamic_fee_test.go | 139 ++++++++++++++++++++++++++++++ itest/intercept_zero_conf_test.go | 8 +- itest/lspd_node.go | 21 ++++- itest/lspd_test.go | 4 + itest/no_balance_test.go | 4 +- itest/probing_test.go | 4 +- itest/tag_test.go | 2 +- itest/test_common.go | 26 ++++-- itest/zero_conf_utxo_test.go | 4 +- itest/zero_reserve_test.go | 4 +- 12 files changed, 199 insertions(+), 25 deletions(-) create mode 100644 itest/dynamic_fee_test.go diff --git a/itest/bob_offline_test.go b/itest/bob_offline_test.go index 45551cb..8b214b7 100644 --- a/itest/bob_offline_test.go +++ b/itest/bob_offline_test.go @@ -23,7 +23,7 @@ func testFailureBobOffline(p *testParams) { log.Printf("Adding bob's invoices") outerAmountMsat := uint64(2100000) - innerAmountMsat := calculateInnerAmountMsat(p.lsp, outerAmountMsat) + innerAmountMsat := calculateInnerAmountMsat(p.lsp, outerAmountMsat, nil) description := "Please pay me" innerInvoice, outerInvoice := GenerateInvoices(p.BreezClient(), generateInvoicesRequest{ @@ -44,7 +44,7 @@ func testFailureBobOffline(p *testParams) { Destination: p.BreezClient().Node().NodeId(), IncomingAmountMsat: int64(outerAmountMsat), OutgoingAmountMsat: int64(innerAmountMsat), - }) + }, false) // Kill the mobile client log.Printf("Stopping breez client") diff --git a/itest/cltv_test.go b/itest/cltv_test.go index 75a9f47..bbe0f5c 100644 --- a/itest/cltv_test.go +++ b/itest/cltv_test.go @@ -23,7 +23,7 @@ func testInvalidCltv(p *testParams) { log.Printf("Adding bob's invoices") outerAmountMsat := uint64(2100000) - innerAmountMsat := calculateInnerAmountMsat(p.lsp, outerAmountMsat) + innerAmountMsat := calculateInnerAmountMsat(p.lsp, outerAmountMsat, nil) description := "Please pay me" innerInvoice, outerInvoice := GenerateInvoices(p.BreezClient(), generateInvoicesRequest{ @@ -43,7 +43,7 @@ func testInvalidCltv(p *testParams) { Destination: p.BreezClient().Node().NodeId(), IncomingAmountMsat: int64(outerAmountMsat), OutgoingAmountMsat: int64(innerAmountMsat), - }) + }, false) // TODO: Fix race waiting for htlc interceptor. log.Printf("Waiting %v to allow htlc interceptor to activate.", htlcInterceptorDelay) diff --git a/itest/dynamic_fee_test.go b/itest/dynamic_fee_test.go new file mode 100644 index 0000000..3763cfd --- /dev/null +++ b/itest/dynamic_fee_test.go @@ -0,0 +1,139 @@ +package itest + +import ( + "log" + "time" + + "github.com/breez/lntest" + lspd "github.com/breez/lspd/rpc" + "github.com/stretchr/testify/assert" +) + +func testDynamicFeeFlow(p *testParams) { + alice := lntest.NewClnNode(p.h, p.m, "Alice") + alice.Start() + alice.Fund(10000000) + p.lsp.LightningNode().Fund(10000000) + + log.Print("Opening channel between Alice and the lsp") + channel := alice.OpenChannel(p.lsp.LightningNode(), &lntest.OpenChannelOptions{ + AmountSat: publicChanAmount, + }) + channelId := alice.WaitForChannelReady(channel) + + log.Printf("Getting channel information") + p.Mempool().SetFees(&RecommendedFeesResponse{ + FastestFee: 3, + HalfHourFee: 3, + HourFee: 3, + EconomyFee: 3, + MinimumFee: 3, + }) + info := ChannelInformation(p.lsp) + assert.Len(p.t, info.OpeningFeeParamsMenu, 1) + params := info.OpeningFeeParamsMenu[0] + assert.Equal(p.t, uint64(3000000), params.MinMsat) + + log.Printf("Adding bob's invoices") + outerAmountMsat := uint64(4200000) + innerAmountMsat := calculateInnerAmountMsat(p.lsp, outerAmountMsat, params) + description := "Please pay me" + innerInvoice, outerInvoice := GenerateInvoices(p.BreezClient(), + generateInvoicesRequest{ + innerAmountMsat: innerAmountMsat, + outerAmountMsat: outerAmountMsat, + description: description, + lsp: p.lsp, + }) + + p.BreezClient().SetHtlcAcceptor(innerAmountMsat) + log.Print("Connecting bob to lspd") + p.BreezClient().Node().ConnectPeer(p.lsp.LightningNode()) + + log.Printf("Testing some bad registrations") + err := RegisterPayment(p.lsp, &lspd.PaymentInformation{ + PaymentHash: innerInvoice.paymentHash, + PaymentSecret: innerInvoice.paymentSecret, + Destination: p.BreezClient().Node().NodeId(), + IncomingAmountMsat: int64(outerAmountMsat), + OutgoingAmountMsat: int64(innerAmountMsat), + OpeningFeeParams: &lspd.OpeningFeeParams{ + // modify minmsat + MinMsat: params.MinMsat + 1, + Proportional: params.Proportional, + ValidUntil: params.ValidUntil, + MaxIdleTime: params.MaxIdleTime, + MaxClientToSelfDelay: params.MaxClientToSelfDelay, + Promise: params.Promise, + }, + }, true) + assert.Contains(p.t, err.Error(), "invalid opening_fee_params") + + err = RegisterPayment(p.lsp, &lspd.PaymentInformation{ + PaymentHash: innerInvoice.paymentHash, + PaymentSecret: innerInvoice.paymentSecret, + Destination: p.BreezClient().Node().NodeId(), + IncomingAmountMsat: int64(outerAmountMsat), + OutgoingAmountMsat: int64(innerAmountMsat), + OpeningFeeParams: &lspd.OpeningFeeParams{ + MinMsat: params.MinMsat, + Proportional: params.Proportional, + ValidUntil: params.ValidUntil, + MaxIdleTime: params.MaxIdleTime, + MaxClientToSelfDelay: params.MaxClientToSelfDelay, + // Modify promise + Promise: params.Promise + "aa", + }, + }, true) + assert.Contains(p.t, err.Error(), "invalid opening_fee_params") + + err = RegisterPayment(p.lsp, &lspd.PaymentInformation{ + PaymentHash: innerInvoice.paymentHash, + PaymentSecret: innerInvoice.paymentSecret, + Destination: p.BreezClient().Node().NodeId(), + // Fee too low + IncomingAmountMsat: int64(2999999), + OutgoingAmountMsat: int64(0), + OpeningFeeParams: params, + }, true) + assert.Contains(p.t, err.Error(), "not enough fees") + + err = RegisterPayment(p.lsp, &lspd.PaymentInformation{ + PaymentHash: innerInvoice.paymentHash, + PaymentSecret: innerInvoice.paymentSecret, + Destination: p.BreezClient().Node().NodeId(), + IncomingAmountMsat: int64(outerAmountMsat), + OutgoingAmountMsat: int64(innerAmountMsat + 1), + OpeningFeeParams: params, + }, true) + assert.Contains(p.t, err.Error(), "not enough fees") + + // Now register the payment for real + log.Printf("Registering payment with lsp") + RegisterPayment(p.lsp, &lspd.PaymentInformation{ + PaymentHash: innerInvoice.paymentHash, + PaymentSecret: innerInvoice.paymentSecret, + Destination: p.BreezClient().Node().NodeId(), + IncomingAmountMsat: int64(outerAmountMsat), + OutgoingAmountMsat: int64(innerAmountMsat), + OpeningFeeParams: info.OpeningFeeParamsMenu[0], + }, false) + + // TODO: Fix race waiting for htlc interceptor. + log.Printf("Waiting %v to allow htlc interceptor to activate.", htlcInterceptorDelay) + <-time.After(htlcInterceptorDelay) + log.Printf("Alice paying") + route := constructRoute(p.lsp.LightningNode(), p.BreezClient().Node(), channelId, lntest.NewShortChanIDFromString("1x0x0"), outerAmountMsat) + payResp, err := alice.PayViaRoute(outerAmountMsat, outerInvoice.paymentHash, outerInvoice.paymentSecret, route) + lntest.CheckError(p.t, err) + bobInvoice := p.BreezClient().Node().GetInvoice(payResp.PaymentHash) + + assert.Equal(p.t, payResp.PaymentPreimage, bobInvoice.PaymentPreimage) + assert.Equal(p.t, innerAmountMsat, bobInvoice.AmountReceivedMsat) + + // Make sure capacity is correct + chans := p.BreezClient().Node().GetChannels() + assert.Equal(p.t, 1, len(chans)) + c := chans[0] + AssertChannelCapacity(p.t, outerAmountMsat, c.CapacityMsat) +} diff --git a/itest/intercept_zero_conf_test.go b/itest/intercept_zero_conf_test.go index 483840f..bc8b82d 100644 --- a/itest/intercept_zero_conf_test.go +++ b/itest/intercept_zero_conf_test.go @@ -25,7 +25,7 @@ func testOpenZeroConfChannelOnReceive(p *testParams) { log.Printf("Adding bob's invoices") outerAmountMsat := uint64(2100000) - innerAmountMsat := calculateInnerAmountMsat(p.lsp, outerAmountMsat) + innerAmountMsat := calculateInnerAmountMsat(p.lsp, outerAmountMsat, nil) description := "Please pay me" innerInvoice, outerInvoice := GenerateInvoices(p.BreezClient(), generateInvoicesRequest{ @@ -46,7 +46,7 @@ func testOpenZeroConfChannelOnReceive(p *testParams) { Destination: p.BreezClient().Node().NodeId(), IncomingAmountMsat: int64(outerAmountMsat), OutgoingAmountMsat: int64(innerAmountMsat), - }) + }, false) // TODO: Fix race waiting for htlc interceptor. log.Printf("Waiting %v to allow htlc interceptor to activate.", htlcInterceptorDelay) @@ -79,7 +79,7 @@ func testOpenZeroConfSingleHtlc(p *testParams) { log.Printf("Adding bob's invoices") outerAmountMsat := uint64(2100000) - innerAmountMsat := calculateInnerAmountMsat(p.lsp, outerAmountMsat) + innerAmountMsat := calculateInnerAmountMsat(p.lsp, outerAmountMsat, nil) description := "Please pay me" innerInvoice, outerInvoice := GenerateInvoices(p.BreezClient(), generateInvoicesRequest{ @@ -100,7 +100,7 @@ func testOpenZeroConfSingleHtlc(p *testParams) { Destination: p.BreezClient().Node().NodeId(), IncomingAmountMsat: int64(outerAmountMsat), OutgoingAmountMsat: int64(innerAmountMsat), - }) + }, false) // TODO: Fix race waiting for htlc interceptor. log.Printf("Waiting %v to allow htlc interceptor to activate.", htlcInterceptorDelay) diff --git a/itest/lspd_node.go b/itest/lspd_node.go index a4100c1..60834dd 100644 --- a/itest/lspd_node.go +++ b/itest/lspd_node.go @@ -223,7 +223,19 @@ func (l *lspBase) Initialize() error { return nil } -func RegisterPayment(l LspNode, paymentInfo *lspd.PaymentInformation) { +func ChannelInformation(l LspNode) *lspd.ChannelInformationReply { + info, err := l.Rpc().ChannelInformation( + l.Harness().Ctx, + &lspd.ChannelInformationRequest{}, + ) + if err != nil { + l.Harness().T.Fatalf("Failed to get ChannelInformation: %v", err) + } + + return info +} + +func RegisterPayment(l LspNode, paymentInfo *lspd.PaymentInformation, continueOnError bool) error { serialized, err := proto.Marshal(paymentInfo) lntest.CheckError(l.Harness().T, err) @@ -237,7 +249,12 @@ func RegisterPayment(l LspNode, paymentInfo *lspd.PaymentInformation) { Blob: encrypted, }, ) - lntest.CheckError(l.Harness().T, err) + + if !continueOnError { + lntest.CheckError(l.Harness().T, err) + } + + return err } func getLspdBinary() (string, error) { diff --git a/itest/lspd_test.go b/itest/lspd_test.go index e6dd3ff..4f403de 100644 --- a/itest/lspd_test.go +++ b/itest/lspd_test.go @@ -147,4 +147,8 @@ var allTestCases = []*testCase{ test: testOpenZeroConfUtxo, skipCreateLsp: true, }, + { + name: "testDynamicFeeFlow", + test: testDynamicFeeFlow, + }, } diff --git a/itest/no_balance_test.go b/itest/no_balance_test.go index 1c93c9d..498edd3 100644 --- a/itest/no_balance_test.go +++ b/itest/no_balance_test.go @@ -22,7 +22,7 @@ func testNoBalance(p *testParams) { log.Printf("Adding bob's invoices") outerAmountMsat := uint64(2100000) - innerAmountMsat := calculateInnerAmountMsat(p.lsp, outerAmountMsat) + innerAmountMsat := calculateInnerAmountMsat(p.lsp, outerAmountMsat, nil) description := "Please pay me" innerInvoice, outerInvoice := GenerateInvoices(p.BreezClient(), generateInvoicesRequest{ @@ -43,7 +43,7 @@ func testNoBalance(p *testParams) { Destination: p.BreezClient().Node().NodeId(), IncomingAmountMsat: int64(outerAmountMsat), OutgoingAmountMsat: int64(innerAmountMsat), - }) + }, false) // TODO: Fix race waiting for htlc interceptor. log.Printf("Waiting %v to allow htlc interceptor to activate.", htlcInterceptorDelay) diff --git a/itest/probing_test.go b/itest/probing_test.go index 15a44b7..3352afe 100644 --- a/itest/probing_test.go +++ b/itest/probing_test.go @@ -24,7 +24,7 @@ func testProbing(p *testParams) { log.Printf("Adding bob's invoices") outerAmountMsat := uint64(2100000) - innerAmountMsat := calculateInnerAmountMsat(p.lsp, outerAmountMsat) + innerAmountMsat := calculateInnerAmountMsat(p.lsp, outerAmountMsat, nil) description := "Please pay me" innerInvoice, outerInvoice := GenerateInvoices(p.BreezClient(), generateInvoicesRequest{ @@ -45,7 +45,7 @@ func testProbing(p *testParams) { Destination: p.BreezClient().Node().NodeId(), IncomingAmountMsat: int64(outerAmountMsat), OutgoingAmountMsat: int64(innerAmountMsat), - }) + }, false) // TODO: Fix race waiting for htlc interceptor. log.Printf("Waiting %v to allow htlc interceptor to activate.", htlcInterceptorDelay) diff --git a/itest/tag_test.go b/itest/tag_test.go index c671dc4..65af104 100644 --- a/itest/tag_test.go +++ b/itest/tag_test.go @@ -23,7 +23,7 @@ func registerPaymentWithTag(p *testParams) { IncomingAmountMsat: int64(25000000), OutgoingAmountMsat: int64(21000000), Tag: expected, - }) + }, false) pgxPool, err := pgxpool.Connect(p.h.Ctx, p.lsp.PostgresBackend().ConnectionString()) if err != nil { diff --git a/itest/test_common.go b/itest/test_common.go index 9d8f666..9a96ae9 100644 --- a/itest/test_common.go +++ b/itest/test_common.go @@ -2,8 +2,10 @@ package itest import ( "crypto/rand" + "log" "testing" + lspd "github.com/breez/lspd/rpc" "github.com/stretchr/testify/assert" ) @@ -26,15 +28,27 @@ func AssertChannelCapacity( assert.Equal(t, ((outerAmountMsat/1000)+100000)*1000, capacityMsat) } -func calculateInnerAmountMsat(lsp LspNode, outerAmountMsat uint64) uint64 { - fee := outerAmountMsat * 40 / 10_000 / 1_000 * 1_000 - if fee < 2000000 { - fee = 2000000 +func calculateInnerAmountMsat(lsp LspNode, outerAmountMsat uint64, params *lspd.OpeningFeeParams) uint64 { + var fee uint64 + log.Printf("%+v", params) + if params == nil { + fee = outerAmountMsat * 40 / 10_000 / 1_000 * 1_000 + if fee < 2000000 { + fee = 2000000 + } + } else { + fee = outerAmountMsat * 40 / 1_000_000 / 1_000 * 1_000 + if fee < params.MinMsat { + fee = params.MinMsat + } } - inner := outerAmountMsat - fee + if fee > outerAmountMsat { + lsp.Harness().Fatalf("Fee is higher than amount") + } - return inner + log.Printf("outer: %v, fee: %v", outerAmountMsat, fee) + return outerAmountMsat - fee } var publicChanAmount uint64 = 1000183 diff --git a/itest/zero_conf_utxo_test.go b/itest/zero_conf_utxo_test.go index 44ab3d1..30ea394 100644 --- a/itest/zero_conf_utxo_test.go +++ b/itest/zero_conf_utxo_test.go @@ -36,7 +36,7 @@ func testOpenZeroConfUtxo(p *testParams) { log.Printf("Adding bob's invoices") outerAmountMsat := uint64(2100000) - innerAmountMsat := calculateInnerAmountMsat(lsp, outerAmountMsat) + innerAmountMsat := calculateInnerAmountMsat(lsp, outerAmountMsat, nil) description := "Please pay me" innerInvoice, outerInvoice := GenerateInvoices(p.BreezClient(), generateInvoicesRequest{ @@ -57,7 +57,7 @@ func testOpenZeroConfUtxo(p *testParams) { Destination: p.BreezClient().Node().NodeId(), IncomingAmountMsat: int64(outerAmountMsat), OutgoingAmountMsat: int64(innerAmountMsat), - }) + }, false) // TODO: Fix race waiting for htlc interceptor. log.Printf("Waiting %v to allow htlc interceptor to activate.", htlcInterceptorDelay) diff --git a/itest/zero_reserve_test.go b/itest/zero_reserve_test.go index 6b1eab3..fad91b3 100644 --- a/itest/zero_reserve_test.go +++ b/itest/zero_reserve_test.go @@ -23,7 +23,7 @@ func testZeroReserve(p *testParams) { log.Printf("Adding bob's invoices") outerAmountMsat := uint64(2100000) - innerAmountMsat := calculateInnerAmountMsat(p.lsp, outerAmountMsat) + innerAmountMsat := calculateInnerAmountMsat(p.lsp, outerAmountMsat, nil) description := "Please pay me" innerInvoice, outerInvoice := GenerateInvoices(p.BreezClient(), generateInvoicesRequest{ @@ -44,7 +44,7 @@ func testZeroReserve(p *testParams) { Destination: p.BreezClient().Node().NodeId(), IncomingAmountMsat: int64(outerAmountMsat), OutgoingAmountMsat: int64(innerAmountMsat), - }) + }, false) // TODO: Fix race waiting for htlc interceptor. log.Printf("Waiting %v to allow htlc interceptor to activate.", htlcInterceptorDelay) From aaccc5fafb8534969acf9804c518a5f8b20d8f2e Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Fri, 12 May 2023 14:04:00 +0200 Subject: [PATCH 176/214] allow configuring an array of opening params --- config/config.go | 27 ++++++++++++----- interceptor/intercept.go | 2 +- itest/lspd_node.go | 17 +++++++---- server.go | 65 +++++++++++++++++++++++----------------- 4 files changed, 70 insertions(+), 41 deletions(-) diff --git a/config/config.go b/config/config.go index 662f1dc..9cd3cce 100644 --- a/config/config.go +++ b/config/config.go @@ -66,8 +66,18 @@ type NodeConfig struct { // The channel can be closed if not used this duration in seconds. MaxInactiveDuration uint64 `json:"maxInactiveDuration,string"` - // The validity duration of an opening params promise. - FeeValidityDuration uint64 `json:"feeValidityDuration,string"` + FeeParams []*FeeParamsSettings `json:"feeParams"` + + // Set this field to connect to an LND node. + Lnd *LndConfig `json:"lnd,omitempty"` + + // Set this field to connect to a CLN node. + Cln *ClnConfig `json:"cln,omitempty"` +} + +type FeeParamsSettings struct { + // The validity duration of an opening params promise in seconds. + ValidityDuration uint64 `json:"validityDuration,string"` // Maximum number of blocks that the client is allowed to set its // `to_self_delay` parameter. @@ -80,13 +90,16 @@ type NodeConfig struct { // fee rate, and take a margin of 20%, the fee multiplication factor should // be 500 * 1.2 * 1000 = 600000. With 20 sat/vbyte, the resulting minimum fee // would be 600000 * 20 = 12000000msat = 12000sat. - FeeMultiplicationFactor uint64 `json:"feeMultiplicationFactor,string"` + MultiplicationFactor uint64 `json:"multiplicationFactor,string"` - // Set this field to connect to an LND node. - Lnd *LndConfig `json:"lnd,omitempty"` + // The proportional fee to charge on channeel opens in ppm. + Proportional uint32 `json:"proportional,string"` - // Set this field to connect to a CLN node. - Cln *ClnConfig `json:"cln,omitempty"` + // The minimum fee to charge for a channel open. + MinimumFeeMsat uint64 `json:"minimumFeeMsat,string"` + + // The maximum idle time in blocks + MaxIdleTime uint32 `json:"maxIdleTime,string"` } type LndConfig struct { diff --git a/interceptor/intercept.go b/interceptor/intercept.go index df5678c..c0a299e 100644 --- a/interceptor/intercept.go +++ b/interceptor/intercept.go @@ -102,7 +102,7 @@ func (i *Interceptor) Intercept(nextHop string, reqPaymentHash []byte, reqOutgoi Proportional: uint32(i.config.ChannelFeePermyriad * 100), ValidUntil: time.Now().UTC().Add(time.Duration(time.Hour * 24)).Format(basetypes.TIME_FORMAT), MaxIdleTime: uint32(i.config.MaxInactiveDuration / 600), - MaxClientToSelfDelay: uint32(i.config.MaxClientToSelfDelay), + MaxClientToSelfDelay: uint32(10000), } } diff --git a/itest/lspd_node.go b/itest/lspd_node.go index 60834dd..7defca3 100644 --- a/itest/lspd_node.go +++ b/itest/lspd_node.go @@ -109,11 +109,18 @@ func newLspd(h *lntest.TestHarness, mem *mempoolApi, name string, nodeConfig *co ChannelMinimumFeeMsat: 2000000, AdditionalChannelCapacity: 100000, MaxInactiveDuration: 3888000, - FeeValidityDuration: 60 * 60 * 24, - MaxClientToSelfDelay: 2016, - FeeMultiplicationFactor: 1000000, - Lnd: lnd, - Cln: cln, + FeeParams: []*config.FeeParamsSettings{ + { + ValidityDuration: 60 * 60 * 24, + MaxClientToSelfDelay: 2016, + MultiplicationFactor: 1000000, + Proportional: 4000, + MinimumFeeMsat: 2000000, + MaxIdleTime: 6480, + }, + }, + Lnd: lnd, + Cln: cln, } if nodeConfig != nil { diff --git a/server.go b/server.go index 361944c..1c06298 100644 --- a/server.go +++ b/server.go @@ -10,6 +10,7 @@ import ( "log" "math" "net" + "sort" "strings" "time" @@ -67,7 +68,7 @@ func (s *server) ChannelInformation(ctx context.Context, in *lspdrpc.ChannelInfo return nil, err } - params, err := s.createOpeningParams(ctx, node) + params, err := s.createOpeningParamsMenu(ctx, node) if err != nil { return nil, err } @@ -86,14 +87,16 @@ func (s *server) ChannelInformation(ctx context.Context, in *lspdrpc.ChannelInfo 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: []*lspdrpc.OpeningFeeParams{params}, + OpeningFeeParamsMenu: params, }, nil } -func (s *server) createOpeningParams( +func (s *server) createOpeningParamsMenu( ctx context.Context, node *node, -) (*lspdrpc.OpeningFeeParams, error) { +) ([]*lspdrpc.OpeningFeeParams, error) { + var menu []*lspdrpc.OpeningFeeParams + // Get a fee estimate. estimate, err := s.feeEstimator.EstimateFeeRate(ctx, s.feeStrategy) if err != nil { @@ -101,33 +104,39 @@ func (s *server) createOpeningParams( return nil, fmt.Errorf("failed to get fee estimate") } - // Multiply the fee estiimate by the configured multiplication factor. - minFeeMsat := estimate.SatPerVByte * - float64(node.nodeConfig.FeeMultiplicationFactor) + for _, setting := range node.nodeConfig.FeeParams { + // Multiply the fee estiimate by the configured multiplication factor. + minFeeMsat := estimate.SatPerVByte * + float64(setting.MultiplicationFactor) - // Make sure the fee is not lower than the minimum fee. - minFeeMsat = math.Max(minFeeMsat, float64(node.nodeConfig.ChannelMinimumFeeMsat)) + // Make sure the fee is not lower than the minimum fee. + minFeeMsat = math.Max(minFeeMsat, float64(setting.MinimumFeeMsat)) - validUntil := time.Now().UTC().Add( - time.Second * time.Duration(node.nodeConfig.FeeValidityDuration), - ) - params := &lspdrpc.OpeningFeeParams{ - MinMsat: uint64(minFeeMsat), - // Proportional is ppm, so divide by 100. - Proportional: uint32(node.nodeConfig.ChannelFeePermyriad / 100), - ValidUntil: validUntil.Format(basetypes.TIME_FORMAT), - // MaxInactiveDuration is in seconds, so divide by 600 for blocks. - MaxIdleTime: uint32(node.nodeConfig.MaxInactiveDuration / 600), - MaxClientToSelfDelay: uint32(node.nodeConfig.MaxClientToSelfDelay), + validUntil := time.Now().UTC().Add( + time.Second * time.Duration(setting.ValidityDuration), + ) + params := &lspdrpc.OpeningFeeParams{ + MinMsat: uint64(minFeeMsat), + Proportional: setting.Proportional, + ValidUntil: validUntil.Format(basetypes.TIME_FORMAT), + MaxIdleTime: setting.MaxIdleTime, + MaxClientToSelfDelay: uint32(setting.MaxClientToSelfDelay), + } + + promise, err := createPromise(node, params) + if err != nil { + log.Printf("Failed to create promise: %v", err) + return nil, err + } + + params.Promise = *promise + menu = append(menu, params) } - promise, err := createPromise(node, params) - if err != nil { - log.Printf("Failed to create promise: %v", err) - } - - params.Promise = *promise - return params, nil + sort.Slice(menu, func(i, j int) bool { + return menu[i].MinMsat < menu[j].MinMsat + }) + return menu, nil } func createPromise(node *node, params *lspdrpc.OpeningFeeParams) (*string, error) { @@ -238,7 +247,7 @@ func (s *server) RegisterPayment( 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), - MaxClientToSelfDelay: uint32(node.nodeConfig.MaxClientToSelfDelay), + MaxClientToSelfDelay: uint32(10000), } } From 21da862da6e6494118387bfd817fb79c88f8277a Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Mon, 15 May 2023 15:11:21 +0200 Subject: [PATCH 177/214] do open channel if promise expired, but cheap fees --- config/config.go | 8 ++++++++ interceptor/intercept.go | 24 +++++++++++++++++++----- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/config/config.go b/config/config.go index 9cd3cce..a2dd6b6 100644 --- a/config/config.go +++ b/config/config.go @@ -68,6 +68,14 @@ type NodeConfig struct { FeeParams []*FeeParamsSettings `json:"feeParams"` + // When a htlc comes in where a channel open is needed, and that payment + // was registered with a promise, but the promise has expired, lspd may + // open the channel anyway if the fee is low enough right now (the promise + // fee was higher than the current fee). ExpiredPromiseMultiplicationFactor + // is the multiplication factor to use on the mempool fee rate to check + // whether the min fee of the promise is lower than the current min fee. + ExpiredPromiseMultiplicationFactor uint64 + // Set this field to connect to an LND node. Lnd *LndConfig `json:"lnd,omitempty"` diff --git a/interceptor/intercept.go b/interceptor/intercept.go index c0a299e..d2fa842 100644 --- a/interceptor/intercept.go +++ b/interceptor/intercept.go @@ -123,11 +123,25 @@ func (i *Interceptor) Intercept(nextHop string, reqPaymentHash []byte, reqOutgoi } if time.Now().UTC().After(validUntil) { - log.Printf("Intercepted expired payment registration. Failing payment. payment hash: %x, valid until: %s", paymentHash, params.ValidUntil) - return InterceptResult{ - Action: INTERCEPT_FAIL_HTLC_WITH_CODE, - FailureCode: FAILURE_TEMPORARY_CHANNEL_FAILURE, - }, nil + feeEstimate, err := i.feeEstimator.EstimateFeeRate(context.Background(), i.feeStrategy) + if err != nil { + log.Printf("Intercepted expired payment registration. Failing payment. payment hash: %x, valid until: %s", paymentHash, params.ValidUntil) + return InterceptResult{ + Action: INTERCEPT_FAIL_HTLC_WITH_CODE, + FailureCode: FAILURE_TEMPORARY_CHANNEL_FAILURE, + }, nil + } + + minMsat := uint64(feeEstimate.SatPerVByte * float64(i.config.ExpiredPromiseMultiplicationFactor)) + if minMsat >= params.MinMsat { + log.Printf("Intercepted expired payment registration. Failing payment. payment hash: %x, valid until: %s", paymentHash, params.ValidUntil) + return InterceptResult{ + Action: INTERCEPT_FAIL_HTLC_WITH_CODE, + FailureCode: FAILURE_TEMPORARY_CHANNEL_FAILURE, + }, nil + } + + log.Printf("Intercepted expired payment registration. Opening channel anyway, because it's cheaper at the current rate. feeEstimate: %v minMsat: %v, params: %+v", feeEstimate.SatPerVByte, minMsat, params) } channelPoint, err = i.openChannel(reqPaymentHash, destination, incomingAmountMsat) From e2912be9be567c65f5d255d4f96720bff2233099 Mon Sep 17 00:00:00 2001 From: Yaacov Akiba Slama Date: Wed, 17 May 2023 08:24:20 +0300 Subject: [PATCH 178/214] Add new_channel_params table --- postgresql/migrations/000011_new_channel_params.down.sql | 1 + postgresql/migrations/000011_new_channel_params.up.sql | 5 +++++ 2 files changed, 6 insertions(+) create mode 100644 postgresql/migrations/000011_new_channel_params.down.sql create mode 100644 postgresql/migrations/000011_new_channel_params.up.sql diff --git a/postgresql/migrations/000011_new_channel_params.down.sql b/postgresql/migrations/000011_new_channel_params.down.sql new file mode 100644 index 0000000..3ae494c --- /dev/null +++ b/postgresql/migrations/000011_new_channel_params.down.sql @@ -0,0 +1 @@ +DROP TABLE public.new_channel_params; diff --git a/postgresql/migrations/000011_new_channel_params.up.sql b/postgresql/migrations/000011_new_channel_params.up.sql new file mode 100644 index 0000000..ccc4414 --- /dev/null +++ b/postgresql/migrations/000011_new_channel_params.up.sql @@ -0,0 +1,5 @@ +CREATE TABLE public.new_channel_params ( + validity int NOT NULL, + params jsonb NOT NULL +); +CREATE UNIQUE INDEX new_channel_params_validity_idx ON public.new_channel_params (validity); From d7aee62fd4068d621d67ec995892d5ecc83ae916 Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Wed, 17 May 2023 12:03:03 +0200 Subject: [PATCH 179/214] use fee params from database --- chain/cached_fee_estimator.go | 61 -------------------------- config/config.go | 37 ---------------- interceptor/intercept.go | 30 ++++++++----- interceptor/store.go | 5 +++ itest/dynamic_fee_test.go | 16 ++++--- itest/lspd_node.go | 82 ++++++++++++++++++++++++++++++----- itest/test_common.go | 2 +- main.go | 3 +- postgresql/intercept_store.go | 33 ++++++++++++++ server.go | 36 ++++----------- 10 files changed, 146 insertions(+), 159 deletions(-) delete mode 100644 chain/cached_fee_estimator.go diff --git a/chain/cached_fee_estimator.go b/chain/cached_fee_estimator.go deleted file mode 100644 index 4f249c6..0000000 --- a/chain/cached_fee_estimator.go +++ /dev/null @@ -1,61 +0,0 @@ -package chain - -import ( - "context" - "sync" - "time" -) - -var cacheDuration time.Duration = time.Minute * 5 - -type feeCache struct { - time time.Time - estimation *FeeEstimation -} - -type CachedFeeEstimator struct { - cache map[FeeStrategy]*feeCache - inner FeeEstimator - mtx sync.Mutex -} - -func NewCachedFeeEstimator(inner FeeEstimator) *CachedFeeEstimator { - return &CachedFeeEstimator{ - inner: inner, - cache: make(map[FeeStrategy]*feeCache), - } -} - -func (e *CachedFeeEstimator) EstimateFeeRate( - ctx context.Context, - strategy FeeStrategy, -) (*FeeEstimation, error) { - - // Make sure we're in a lock, because we're reading/writing a map. - e.mtx.Lock() - defer e.mtx.Unlock() - - // See if there's a cached value first. - cached, ok := e.cache[strategy] - - // If there is and it's still valid, return that. - if ok && cached.time.Add(cacheDuration).After(time.Now()) { - return cached.estimation, nil - } - - // There was no valid cache. - // Fetch the new fee estimate. - now := time.Now() - estimation, err := e.inner.EstimateFeeRate(ctx, strategy) - if err != nil { - return nil, err - } - - // Cache it. - e.cache[strategy] = &feeCache{ - time: now, - estimation: estimation, - } - - return estimation, nil -} diff --git a/config/config.go b/config/config.go index a2dd6b6..4587f85 100644 --- a/config/config.go +++ b/config/config.go @@ -66,16 +66,6 @@ type NodeConfig struct { // The channel can be closed if not used this duration in seconds. MaxInactiveDuration uint64 `json:"maxInactiveDuration,string"` - FeeParams []*FeeParamsSettings `json:"feeParams"` - - // When a htlc comes in where a channel open is needed, and that payment - // was registered with a promise, but the promise has expired, lspd may - // open the channel anyway if the fee is low enough right now (the promise - // fee was higher than the current fee). ExpiredPromiseMultiplicationFactor - // is the multiplication factor to use on the mempool fee rate to check - // whether the min fee of the promise is lower than the current min fee. - ExpiredPromiseMultiplicationFactor uint64 - // Set this field to connect to an LND node. Lnd *LndConfig `json:"lnd,omitempty"` @@ -83,33 +73,6 @@ type NodeConfig struct { Cln *ClnConfig `json:"cln,omitempty"` } -type FeeParamsSettings struct { - // The validity duration of an opening params promise in seconds. - ValidityDuration uint64 `json:"validityDuration,string"` - - // Maximum number of blocks that the client is allowed to set its - // `to_self_delay` parameter. - MaxClientToSelfDelay uint64 `json:"maxClientToSelfDelay,string"` - - // Multiplication factor to calculate the minimum fee for a JIT channel open. - // The resulting fee after multiplying sat/vbyte by the multiplication factor - // is denominated in millisat. - // e.g. if you expect to publish 500 bytes onchain with the given sat/vbyte - // fee rate, and take a margin of 20%, the fee multiplication factor should - // be 500 * 1.2 * 1000 = 600000. With 20 sat/vbyte, the resulting minimum fee - // would be 600000 * 20 = 12000000msat = 12000sat. - MultiplicationFactor uint64 `json:"multiplicationFactor,string"` - - // The proportional fee to charge on channeel opens in ppm. - Proportional uint32 `json:"proportional,string"` - - // The minimum fee to charge for a channel open. - MinimumFeeMsat uint64 `json:"minimumFeeMsat,string"` - - // The maximum idle time in blocks - MaxIdleTime uint32 `json:"maxIdleTime,string"` -} - type LndConfig struct { // Address to the grpc api. Address string `json:"address"` diff --git a/interceptor/intercept.go b/interceptor/intercept.go index d2fa842..69b8178 100644 --- a/interceptor/intercept.go +++ b/interceptor/intercept.go @@ -123,8 +123,7 @@ func (i *Interceptor) Intercept(nextHop string, reqPaymentHash []byte, reqOutgoi } if time.Now().UTC().After(validUntil) { - feeEstimate, err := i.feeEstimator.EstimateFeeRate(context.Background(), i.feeStrategy) - if err != nil { + if !i.isCurrentChainFeeCheaper(params) { log.Printf("Intercepted expired payment registration. Failing payment. payment hash: %x, valid until: %s", paymentHash, params.ValidUntil) return InterceptResult{ Action: INTERCEPT_FAIL_HTLC_WITH_CODE, @@ -132,16 +131,7 @@ func (i *Interceptor) Intercept(nextHop string, reqPaymentHash []byte, reqOutgoi }, nil } - minMsat := uint64(feeEstimate.SatPerVByte * float64(i.config.ExpiredPromiseMultiplicationFactor)) - if minMsat >= params.MinMsat { - log.Printf("Intercepted expired payment registration. Failing payment. payment hash: %x, valid until: %s", paymentHash, params.ValidUntil) - return InterceptResult{ - Action: INTERCEPT_FAIL_HTLC_WITH_CODE, - FailureCode: FAILURE_TEMPORARY_CHANNEL_FAILURE, - }, nil - } - - log.Printf("Intercepted expired payment registration. Opening channel anyway, because it's cheaper at the current rate. feeEstimate: %v minMsat: %v, params: %+v", feeEstimate.SatPerVByte, minMsat, params) + log.Printf("Intercepted expired payment registration. Opening channel anyway, because it's cheaper at the current rate. %+v", params) } channelPoint, err = i.openChannel(reqPaymentHash, destination, incomingAmountMsat) @@ -291,6 +281,22 @@ func (i *Interceptor) Intercept(nextHop string, reqPaymentHash []byte, reqOutgoi return resp.(InterceptResult) } +func (i *Interceptor) isCurrentChainFeeCheaper(params *OpeningFeeParams) bool { + settings, err := i.store.GetFeeParamsSettings() + if err != nil { + log.Printf("Failed to get fee params settings: %v", err) + return false + } + + for _, setting := range settings { + if setting.Params.MinMsat <= params.MinMsat { + return true + } + } + + return false +} + func (i *Interceptor) openChannel(paymentHash, destination []byte, incomingAmountMsat int64) (*wire.OutPoint, error) { capacity := incomingAmountMsat/1000 + i.config.AdditionalChannelCapacity if capacity == i.config.PublicChannelAmount { diff --git a/interceptor/store.go b/interceptor/store.go index eebb2f3..605f9cd 100644 --- a/interceptor/store.go +++ b/interceptor/store.go @@ -6,6 +6,10 @@ import ( "github.com/btcsuite/btcd/wire" ) +type OpeningFeeParamsSetting struct { + Validity time.Duration + Params *OpeningFeeParams +} type OpeningFeeParams struct { MinMsat uint64 `json:"min_msat,string"` Proportional uint32 `json:"proportional"` @@ -20,4 +24,5 @@ type InterceptStore interface { SetFundingTx(paymentHash []byte, channelPoint *wire.OutPoint) error RegisterPayment(params *OpeningFeeParams, destination, paymentHash, paymentSecret []byte, incomingAmountMsat, outgoingAmountMsat int64, tag string) error InsertChannel(initialChanID, confirmedChanId uint64, channelPoint string, nodeID []byte, lastUpdate time.Time) error + GetFeeParamsSettings() ([]*OpeningFeeParamsSetting, error) } diff --git a/itest/dynamic_fee_test.go b/itest/dynamic_fee_test.go index 3763cfd..7098add 100644 --- a/itest/dynamic_fee_test.go +++ b/itest/dynamic_fee_test.go @@ -22,18 +22,20 @@ func testDynamicFeeFlow(p *testParams) { channelId := alice.WaitForChannelReady(channel) log.Printf("Getting channel information") - p.Mempool().SetFees(&RecommendedFeesResponse{ - FastestFee: 3, - HalfHourFee: 3, - HourFee: 3, - EconomyFee: 3, - MinimumFee: 3, - }) + SetFeeParams(p.lsp, []*FeeParamSetting{ + { + Validity: time.Second * 3600, + MinMsat: 3000000, + Proportional: 1000, + }, + }, + ) info := ChannelInformation(p.lsp) assert.Len(p.t, info.OpeningFeeParamsMenu, 1) params := info.OpeningFeeParamsMenu[0] assert.Equal(p.t, uint64(3000000), params.MinMsat) + log.Printf("opening_fee_params: %+v", params) log.Printf("Adding bob's invoices") outerAmountMsat := uint64(4200000) innerAmountMsat := calculateInnerAmountMsat(p.lsp, outerAmountMsat, params) diff --git a/itest/lspd_node.go b/itest/lspd_node.go index 7defca3..1e4c825 100644 --- a/itest/lspd_node.go +++ b/itest/lspd_node.go @@ -11,6 +11,7 @@ import ( "os" "os/exec" "path/filepath" + "time" "github.com/breez/lntest" "github.com/breez/lspd/config" @@ -19,6 +20,7 @@ import ( "github.com/decred/dcrd/dcrec/secp256k1/v4" ecies "github.com/ecies/go/v2" "github.com/golang/protobuf/proto" + "github.com/jackc/pgx/v4/pgxpool" ) var ( @@ -109,18 +111,8 @@ func newLspd(h *lntest.TestHarness, mem *mempoolApi, name string, nodeConfig *co ChannelMinimumFeeMsat: 2000000, AdditionalChannelCapacity: 100000, MaxInactiveDuration: 3888000, - FeeParams: []*config.FeeParamsSettings{ - { - ValidityDuration: 60 * 60 * 24, - MaxClientToSelfDelay: 2016, - MultiplicationFactor: 1000000, - Proportional: 4000, - MinimumFeeMsat: 2000000, - MaxIdleTime: 6480, - }, - }, - Lnd: lnd, - Cln: cln, + Lnd: lnd, + Cln: cln, } if nodeConfig != nil { @@ -192,6 +184,25 @@ func (l *lspBase) Initialize() error { return err } + pgxPool, err := pgxpool.Connect(l.harness.Ctx, l.postgresBackend.ConnectionString()) + if err != nil { + lntest.PerformCleanup(cleanups) + return fmt.Errorf("failed to connect to postgres: %w", err) + } + defer pgxPool.Close() + + _, err = pgxPool.Exec( + l.harness.Ctx, + `INSERT INTO new_channel_params (validity, params) + VALUES + (3600, '{"min_msat": "1000000", "proportional": 7500, "max_idle_time": 4320, "max_client_to_self_delay": 432}'), + (259200, '{"min_msat": "1100000", "proportional": 7500, "max_idle_time": 4320, "max_client_to_self_delay": 432}');`, + ) + if err != nil { + lntest.PerformCleanup(cleanups) + return fmt.Errorf("failed to insert new_channel_params: %w", err) + } + log.Printf("%s: Creating lspd startup script at %s", l.name, l.scriptFilePath) scriptFile, err := os.OpenFile(l.scriptFilePath, os.O_CREATE|os.O_WRONLY, 0755) if err != nil { @@ -264,6 +275,53 @@ func RegisterPayment(l LspNode, paymentInfo *lspd.PaymentInformation, continueOn return err } +type FeeParamSetting struct { + Validity time.Duration + MinMsat uint64 + Proportional uint32 +} + +func SetFeeParams(l LspNode, settings []*FeeParamSetting) error { + pgxPool, err := pgxpool.Connect(l.Harness().Ctx, l.PostgresBackend().ConnectionString()) + if err != nil { + return fmt.Errorf("failed to connect to postgres: %w", err) + } + defer pgxPool.Close() + + _, err = pgxPool.Exec(l.Harness().Ctx, "DELETE FROM new_channel_params") + if err != nil { + return fmt.Errorf("failed to delete new_channel_params: %w", err) + } + + if len(settings) == 0 { + return nil + } + + query := `INSERT INTO new_channel_params (validity, params) VALUES ` + first := true + for _, setting := range settings { + if !first { + query += `,` + } + + query += fmt.Sprintf( + `(%d, '{"min_msat": "%d", "proportional": %d, "max_idle_time": 4320, "max_client_to_self_delay": 432}')`, + int64(setting.Validity.Seconds()), + setting.MinMsat, + setting.Proportional, + ) + + first = false + } + query += `;` + _, err = pgxPool.Exec(l.Harness().Ctx, query) + if err != nil { + return fmt.Errorf("failed to insert new_channel_params: %w", err) + } + + return nil +} + func getLspdBinary() (string, error) { if lspdExecutable != nil { return *lspdExecutable, nil diff --git a/itest/test_common.go b/itest/test_common.go index 9a96ae9..76fe7e0 100644 --- a/itest/test_common.go +++ b/itest/test_common.go @@ -37,7 +37,7 @@ func calculateInnerAmountMsat(lsp LspNode, outerAmountMsat uint64, params *lspd. fee = 2000000 } } else { - fee = outerAmountMsat * 40 / 1_000_000 / 1_000 * 1_000 + fee = outerAmountMsat * uint64(params.Proportional) / 1_000_000 / 1_000 * 1_000 if fee < params.MinMsat { fee = params.MinMsat } diff --git a/main.go b/main.go index 784622c..2e3df49 100644 --- a/main.go +++ b/main.go @@ -117,8 +117,7 @@ func main() { address := os.Getenv("LISTEN_ADDRESS") certMagicDomain := os.Getenv("CERTMAGIC_DOMAIN") - cachedEstimator := chain.NewCachedFeeEstimator(feeEstimator) - s, err := NewGrpcServer(nodes, address, certMagicDomain, interceptStore, feeStrategy, cachedEstimator) + s, err := NewGrpcServer(nodes, address, certMagicDomain, interceptStore) if err != nil { log.Fatalf("failed to initialize grpc server: %v", err) } diff --git a/postgresql/intercept_store.go b/postgresql/intercept_store.go index 1aef836..05fe591 100644 --- a/postgresql/intercept_store.go +++ b/postgresql/intercept_store.go @@ -118,3 +118,36 @@ func (s *PostgresInterceptStore) InsertChannel(initialChanID, confirmedChanId ui initialChanID, confirmedChanId, nodeID, c.String()) return nil } + +func (s *PostgresInterceptStore) GetFeeParamsSettings() ([]*interceptor.OpeningFeeParamsSetting, error) { + rows, err := s.pool.Query(context.Background(), `SELECT validity, params FROM new_channel_params`) + if err != nil { + log.Printf("GetFeeParamsSettings() error: %v", err) + return nil, err + } + + var settings []*interceptor.OpeningFeeParamsSetting + for rows.Next() { + var validity int64 + var param string + err = rows.Scan(&validity, ¶m) + if err != nil { + return nil, err + } + + var params *interceptor.OpeningFeeParams + err := json.Unmarshal([]byte(param), ¶ms) + if err != nil { + log.Printf("Failed to unmarshal fee param '%v': %v", param, err) + return nil, err + } + + duration := time.Second * time.Duration(validity) + settings = append(settings, &interceptor.OpeningFeeParamsSetting{ + Validity: duration, + Params: params, + }) + } + + return settings, nil +} diff --git a/server.go b/server.go index 1c06298..4226c4b 100644 --- a/server.go +++ b/server.go @@ -8,7 +8,6 @@ import ( "encoding/json" "fmt" "log" - "math" "net" "sort" "strings" @@ -16,7 +15,6 @@ import ( "github.com/breez/lspd/basetypes" "github.com/breez/lspd/btceclegacy" - "github.com/breez/lspd/chain" "github.com/breez/lspd/cln" "github.com/breez/lspd/config" "github.com/breez/lspd/interceptor" @@ -48,8 +46,6 @@ type server struct { s *grpc.Server nodes map[string]*node store interceptor.InterceptStore - feeStrategy chain.FeeStrategy - feeEstimator chain.FeeEstimator } type node struct { @@ -97,30 +93,20 @@ func (s *server) createOpeningParamsMenu( ) ([]*lspdrpc.OpeningFeeParams, error) { var menu []*lspdrpc.OpeningFeeParams - // Get a fee estimate. - estimate, err := s.feeEstimator.EstimateFeeRate(ctx, s.feeStrategy) + settings, err := s.store.GetFeeParamsSettings() if err != nil { - log.Printf("Failed to get fee estimate: %v", err) - return nil, fmt.Errorf("failed to get fee estimate") + log.Printf("Failed to fetch fee params settings: %v", err) + return nil, fmt.Errorf("failed to get opening_fee_params") } - for _, setting := range node.nodeConfig.FeeParams { - // Multiply the fee estiimate by the configured multiplication factor. - minFeeMsat := estimate.SatPerVByte * - float64(setting.MultiplicationFactor) - - // Make sure the fee is not lower than the minimum fee. - minFeeMsat = math.Max(minFeeMsat, float64(setting.MinimumFeeMsat)) - - validUntil := time.Now().UTC().Add( - time.Second * time.Duration(setting.ValidityDuration), - ) + for _, setting := range settings { + validUntil := time.Now().UTC().Add(setting.Validity) params := &lspdrpc.OpeningFeeParams{ - MinMsat: uint64(minFeeMsat), - Proportional: setting.Proportional, + MinMsat: setting.Params.MinMsat, + Proportional: setting.Params.Proportional, ValidUntil: validUntil.Format(basetypes.TIME_FORMAT), - MaxIdleTime: setting.MaxIdleTime, - MaxClientToSelfDelay: uint32(setting.MaxClientToSelfDelay), + MaxIdleTime: setting.Params.MaxIdleTime, + MaxClientToSelfDelay: setting.Params.MaxClientToSelfDelay, } promise, err := createPromise(node, params) @@ -416,8 +402,6 @@ func NewGrpcServer( address string, certmagicDomain string, store interceptor.InterceptStore, - feeStrategy chain.FeeStrategy, - feeEstimator chain.FeeEstimator, ) (*server, error) { if len(configs) == 0 { return nil, fmt.Errorf("no nodes supplied") @@ -477,8 +461,6 @@ func NewGrpcServer( certmagicDomain: certmagicDomain, nodes: nodes, store: store, - feeStrategy: feeStrategy, - feeEstimator: feeEstimator, }, nil } From 301760401fe1ff988b2e0b1500f1f1516cd9e9af Mon Sep 17 00:00:00 2001 From: Yaacov Akiba Slama Date: Wed, 24 May 2023 15:31:36 +0300 Subject: [PATCH 180/214] Use the full signature as promise and verify it directly --- server.go | 40 +++++++++++++++++++++++++++++----------- 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/server.go b/server.go index 4226c4b..c1dc688 100644 --- a/server.go +++ b/server.go @@ -125,8 +125,7 @@ func (s *server) createOpeningParamsMenu( return menu, nil } -func createPromise(node *node, params *lspdrpc.OpeningFeeParams) (*string, error) { - +func paramsHash(params *lspdrpc.OpeningFeeParams) ([]byte, error) { // First hash all the values in the params in a fixed order. items := []interface{}{ params.MinMsat, @@ -140,33 +139,52 @@ func createPromise(node *node, params *lspdrpc.OpeningFeeParams) (*string, error return nil, err } hash := sha256.Sum256(blob) + return hash[:], nil +} +func createPromise(node *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) if err != nil { return nil, err } - - // The promise is the hex encoded hash of the signature. - result := sha256.Sum256(sig) - promise := hex.EncodeToString(result[:]) + promise := hex.EncodeToString(sig) return &promise, nil } +func verifyPromise(node *node, params *lspdrpc.OpeningFeeParams) error { + hash, err := paramsHash(params) + if err != nil { + return err + } + sig, err := hex.DecodeString(params.Promise) + if err != nil { + return err + } + pub, _, err := ecdsa.RecoverCompact(sig, hash) + if err != nil { + return err + } + if !node.publicKey.IsEqual(pub) { + return fmt.Errorf("invalid promise") + } + return nil +} + func validateOpeningFeeParams(node *node, params *lspdrpc.OpeningFeeParams) bool { if params == nil { return false } - promise, err := createPromise(node, params) + err := verifyPromise(node, params) if err != nil { return false } - if *promise != params.Promise { - return false - } - t, err := time.Parse(basetypes.TIME_FORMAT, params.ValidUntil) if err != nil { return false From 2c19c5389332929d3076f7b90de42fc0a5d6d3a9 Mon Sep 17 00:00:00 2001 From: Yaacov Akiba Slama Date: Wed, 24 May 2023 16:27:52 +0300 Subject: [PATCH 181/214] Add some logs --- server.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/server.go b/server.go index c1dc688..de10bd2 100644 --- a/server.go +++ b/server.go @@ -136,6 +136,7 @@ func paramsHash(params *lspdrpc.OpeningFeeParams) ([]byte, error) { } blob, err := json.Marshal(items) if err != nil { + log.Printf("paramsHash error: %v", err) return nil, err } hash := sha256.Sum256(blob) @@ -150,6 +151,7 @@ func createPromise(node *node, params *lspdrpc.OpeningFeeParams) (*string, error // Sign the hash with the private key of the LSP id. sig, err := ecdsa.SignCompact(node.privateKey, hash[:], true) if err != nil { + log.Printf("createPromise: SignCompact error: %v", err) return nil, err } promise := hex.EncodeToString(sig) @@ -163,13 +165,16 @@ func verifyPromise(node *node, params *lspdrpc.OpeningFeeParams) error { } sig, err := hex.DecodeString(params.Promise) if err != nil { + log.Printf("verifyPromise: hex.DecodeString error: %v", err) return err } pub, _, err := ecdsa.RecoverCompact(sig, hash) if err != nil { + log.Printf("verifyPromise: RecoverCompact(%x) error: %v", sig, err) return err } if !node.publicKey.IsEqual(pub) { + log.Print("verifyPromise: not signed by us", err) return fmt.Errorf("invalid promise") } return nil @@ -187,10 +192,12 @@ func validateOpeningFeeParams(node *node, params *lspdrpc.OpeningFeeParams) bool t, err := time.Parse(basetypes.TIME_FORMAT, params.ValidUntil) if err != nil { + log.Printf("validateOpeningFeeParams: time.Parse(%v, %v) error: %v", basetypes.TIME_FORMAT, params.ValidUntil, err) return false } if time.Now().UTC().After(t) { + log.Printf("validateOpeningFeeParams: promise not valid anymore: %v", t) return false } From 59f01bd30c040b3ba61a3c9990e69d52d5d601a8 Mon Sep 17 00:00:00 2001 From: Yaacov Akiba Slama Date: Sun, 28 May 2023 13:23:34 +0300 Subject: [PATCH 182/214] Add data in new_channel_params in the migration --- postgresql/migrations/000011_new_channel_params.up.sql | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/postgresql/migrations/000011_new_channel_params.up.sql b/postgresql/migrations/000011_new_channel_params.up.sql index ccc4414..126de8b 100644 --- a/postgresql/migrations/000011_new_channel_params.up.sql +++ b/postgresql/migrations/000011_new_channel_params.up.sql @@ -3,3 +3,9 @@ CREATE TABLE public.new_channel_params ( params jsonb NOT NULL ); CREATE UNIQUE INDEX new_channel_params_validity_idx ON public.new_channel_params (validity); + +INSERT INTO public.new_channel_params (validity, params) + VALUES(259200, '{"min_msat": "12000000", "proportional": 7500, "max_idle_time": 4320, "max_client_to_self_delay": 432}'::jsonb); + +INSERT INTO public.new_channel_params (validity, params) + VALUES(3600, '{"min_msat": "10000000", "proportional": 7500, "max_idle_time": 4320, "max_client_to_self_delay": 432}'::jsonb); From 057c5582c1aedbd08dc38263f740ed9495dadd7f Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Fri, 2 Jun 2023 13:32:45 +0200 Subject: [PATCH 183/214] fix integration tests --- itest/lspd_node.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/itest/lspd_node.go b/itest/lspd_node.go index 1e4c825..1f49398 100644 --- a/itest/lspd_node.go +++ b/itest/lspd_node.go @@ -191,6 +191,15 @@ func (l *lspBase) Initialize() error { } defer pgxPool.Close() + _, err = pgxPool.Exec( + l.harness.Ctx, + `DELETE FROM new_channel_params`, + ) + if err != nil { + lntest.PerformCleanup(cleanups) + return fmt.Errorf("failed to delete new_channel_params: %w", err) + } + _, err = pgxPool.Exec( l.harness.Ctx, `INSERT INTO new_channel_params (validity, params) From c2b1b841b4da613014cc78e2b67d8633ecbbbea8 Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Fri, 2 Jun 2023 13:32:49 +0200 Subject: [PATCH 184/214] add tag to open channel email --- interceptor/email.go | 10 +++++++++- interceptor/intercept.go | 7 ++++--- interceptor/store.go | 2 +- postgresql/intercept_store.go | 14 +++++++------- 4 files changed, 21 insertions(+), 12 deletions(-) diff --git a/interceptor/email.go b/interceptor/email.go index 857aacf..8777cac 100644 --- a/interceptor/email.go +++ b/interceptor/email.go @@ -124,7 +124,9 @@ func sendChannelMismatchNotification(nodeID string, notFakeChannels, closedChann func sendOpenChannelEmailNotification( paymentHash []byte, incomingAmountMsat int64, destination []byte, capacity int64, - channelPoint string) error { + channelPoint string, + tag *string, +) error { var html bytes.Buffer tpl := ` @@ -134,6 +136,7 @@ func sendOpenChannelEmailNotification( Destination Node:{{ .Destination }} Channel capacity (sat):{{ .Capacity }} Channel point:{{ .ChannelPoint }} + Tag:{{ .Tag }} ` t, err := template.New("OpenChannelEmail").Parse(tpl) @@ -141,12 +144,17 @@ func sendOpenChannelEmailNotification( return err } + tagStr := "" + if tag != nil { + tagStr = *tag + } if err := t.Execute(&html, map[string]string{ "PaymentHash": hex.EncodeToString(paymentHash), "IncomingAmountMsat": strconv.FormatUint(uint64(incomingAmountMsat), 10), "Destination": hex.EncodeToString(destination), "Capacity": strconv.FormatUint(uint64(capacity), 10), "ChannelPoint": channelPoint, + "Tag": tagStr, }); err != nil { return err } diff --git a/interceptor/intercept.go b/interceptor/intercept.go index 69b8178..351cabe 100644 --- a/interceptor/intercept.go +++ b/interceptor/intercept.go @@ -76,7 +76,7 @@ func NewInterceptor( func (i *Interceptor) Intercept(nextHop string, reqPaymentHash []byte, reqOutgoingAmountMsat uint64, reqOutgoingExpiry uint32, reqIncomingExpiry uint32) InterceptResult { reqPaymentHashStr := hex.EncodeToString(reqPaymentHash) resp, _, _ := i.payHashGroup.Do(reqPaymentHashStr, func() (interface{}, error) { - params, paymentHash, paymentSecret, destination, incomingAmountMsat, outgoingAmountMsat, channelPoint, err := i.store.PaymentInfo(reqPaymentHash) + params, paymentHash, paymentSecret, destination, incomingAmountMsat, outgoingAmountMsat, channelPoint, tag, err := i.store.PaymentInfo(reqPaymentHash) if err != nil { log.Printf("paymentInfo(%x) error: %v", reqPaymentHash, err) return InterceptResult{ @@ -134,7 +134,7 @@ func (i *Interceptor) Intercept(nextHop string, reqPaymentHash []byte, reqOutgoi log.Printf("Intercepted expired payment registration. Opening channel anyway, because it's cheaper at the current rate. %+v", params) } - channelPoint, err = i.openChannel(reqPaymentHash, destination, incomingAmountMsat) + channelPoint, err = i.openChannel(reqPaymentHash, destination, incomingAmountMsat, tag) if err != nil { log.Printf("openChannel(%x, %v) err: %v", destination, incomingAmountMsat, err) return InterceptResult{ @@ -297,7 +297,7 @@ func (i *Interceptor) isCurrentChainFeeCheaper(params *OpeningFeeParams) bool { return false } -func (i *Interceptor) openChannel(paymentHash, destination []byte, incomingAmountMsat int64) (*wire.OutPoint, error) { +func (i *Interceptor) openChannel(paymentHash, destination []byte, incomingAmountMsat int64, tag *string) (*wire.OutPoint, error) { capacity := incomingAmountMsat/1000 + i.config.AdditionalChannelCapacity if capacity == i.config.PublicChannelAmount { capacity++ @@ -348,6 +348,7 @@ func (i *Interceptor) openChannel(paymentHash, destination []byte, incomingAmoun destination, capacity, channelPoint.String(), + tag, ) err = i.store.SetFundingTx(paymentHash, channelPoint) return channelPoint, err diff --git a/interceptor/store.go b/interceptor/store.go index 605f9cd..3863139 100644 --- a/interceptor/store.go +++ b/interceptor/store.go @@ -20,7 +20,7 @@ type OpeningFeeParams struct { } type InterceptStore interface { - PaymentInfo(htlcPaymentHash []byte) (*OpeningFeeParams, []byte, []byte, []byte, int64, int64, *wire.OutPoint, error) + PaymentInfo(htlcPaymentHash []byte) (*OpeningFeeParams, []byte, []byte, []byte, int64, int64, *wire.OutPoint, *string, error) SetFundingTx(paymentHash []byte, channelPoint *wire.OutPoint) error RegisterPayment(params *OpeningFeeParams, destination, paymentHash, paymentSecret []byte, incomingAmountMsat, outgoingAmountMsat int64, tag string) error InsertChannel(initialChanID, confirmedChanId uint64, channelPoint string, nodeID []byte, lastUpdate time.Time) error diff --git a/postgresql/intercept_store.go b/postgresql/intercept_store.go index 05fe591..31e0acb 100644 --- a/postgresql/intercept_store.go +++ b/postgresql/intercept_store.go @@ -23,24 +23,24 @@ func NewPostgresInterceptStore(pool *pgxpool.Pool) *PostgresInterceptStore { return &PostgresInterceptStore{pool: pool} } -func (s *PostgresInterceptStore) PaymentInfo(htlcPaymentHash []byte) (*interceptor.OpeningFeeParams, []byte, []byte, []byte, int64, int64, *wire.OutPoint, error) { +func (s *PostgresInterceptStore) PaymentInfo(htlcPaymentHash []byte) (*interceptor.OpeningFeeParams, []byte, []byte, []byte, int64, int64, *wire.OutPoint, *string, error) { var ( - p *string + p, tag *string paymentHash, paymentSecret, destination []byte incomingAmountMsat, outgoingAmountMsat int64 fundingTxID []byte fundingTxOutnum pgtype.Int4 ) err := s.pool.QueryRow(context.Background(), - `SELECT payment_hash, payment_secret, destination, incoming_amount_msat, outgoing_amount_msat, funding_tx_id, funding_tx_outnum, opening_fee_params + `SELECT payment_hash, payment_secret, destination, incoming_amount_msat, outgoing_amount_msat, funding_tx_id, funding_tx_outnum, opening_fee_params, tag FROM payments WHERE payment_hash=$1 OR sha256('probing-01:' || payment_hash)=$1`, - htlcPaymentHash).Scan(&paymentHash, &paymentSecret, &destination, &incomingAmountMsat, &outgoingAmountMsat, &fundingTxID, &fundingTxOutnum, &p) + htlcPaymentHash).Scan(&paymentHash, &paymentSecret, &destination, &incomingAmountMsat, &outgoingAmountMsat, &fundingTxID, &fundingTxOutnum, &p, &tag) if err != nil { if err == pgx.ErrNoRows { err = nil } - return nil, nil, nil, nil, 0, 0, nil, err + return nil, nil, nil, nil, 0, 0, nil, nil, err } var cp *wire.OutPoint @@ -56,10 +56,10 @@ func (s *PostgresInterceptStore) PaymentInfo(htlcPaymentHash []byte) (*intercept err = json.Unmarshal([]byte(*p), ¶ms) if err != nil { log.Printf("Failed to unmarshal OpeningFeeParams '%s': %v", *p, err) - return nil, nil, nil, nil, 0, 0, nil, err + return nil, nil, nil, nil, 0, 0, nil, nil, err } } - return params, paymentHash, paymentSecret, destination, incomingAmountMsat, outgoingAmountMsat, cp, nil + return params, paymentHash, paymentSecret, destination, incomingAmountMsat, outgoingAmountMsat, cp, tag, nil } func (s *PostgresInterceptStore) SetFundingTx(paymentHash []byte, channelPoint *wire.OutPoint) error { From 81f24accadc2859b4201b48b1fb7c8fa57ac95c7 Mon Sep 17 00:00:00 2001 From: Yaacov Akiba Slama Date: Fri, 2 Jun 2023 18:51:52 +0300 Subject: [PATCH 185/214] Support more than one token per node and params per token --- config/config.go | 4 +- interceptor/intercept.go | 8 +-- interceptor/store.go | 6 +-- postgresql/intercept_store.go | 37 ++++++++----- .../000012_new_channel_params_token.down.sql | 2 + .../000012_new_channel_params_token.up.sql | 1 + server.go | 54 +++++++++++-------- 7 files changed, 66 insertions(+), 46 deletions(-) create mode 100644 postgresql/migrations/000012_new_channel_params_token.down.sql create mode 100644 postgresql/migrations/000012_new_channel_params_token.up.sql diff --git a/config/config.go b/config/config.go index 4587f85..0f18c94 100644 --- a/config/config.go +++ b/config/config.go @@ -11,9 +11,9 @@ type NodeConfig struct { // clients. LspdPrivateKey string `json:"lspdPrivateKey"` - // Token used to authenticate to lspd. This token must be unique for each + // Tokens used to authenticate to lspd. These tokens must be unique for each // configured node, so it's obvious which node an rpc call is meant for. - Token string `json:"token"` + Tokens []string `json:"tokens"` // The network location of the lightning node, e.g. `12.34.56.78:9012` or // `localhost:10011` diff --git a/interceptor/intercept.go b/interceptor/intercept.go index 69b8178..6cc751b 100644 --- a/interceptor/intercept.go +++ b/interceptor/intercept.go @@ -76,7 +76,7 @@ func NewInterceptor( func (i *Interceptor) Intercept(nextHop string, reqPaymentHash []byte, reqOutgoingAmountMsat uint64, reqOutgoingExpiry uint32, reqIncomingExpiry uint32) InterceptResult { reqPaymentHashStr := hex.EncodeToString(reqPaymentHash) resp, _, _ := i.payHashGroup.Do(reqPaymentHashStr, func() (interface{}, error) { - params, paymentHash, paymentSecret, destination, incomingAmountMsat, outgoingAmountMsat, channelPoint, err := i.store.PaymentInfo(reqPaymentHash) + token, params, paymentHash, paymentSecret, destination, incomingAmountMsat, outgoingAmountMsat, channelPoint, err := i.store.PaymentInfo(reqPaymentHash) if err != nil { log.Printf("paymentInfo(%x) error: %v", reqPaymentHash, err) return InterceptResult{ @@ -123,7 +123,7 @@ func (i *Interceptor) Intercept(nextHop string, reqPaymentHash []byte, reqOutgoi } if time.Now().UTC().After(validUntil) { - if !i.isCurrentChainFeeCheaper(params) { + if !i.isCurrentChainFeeCheaper(token, params) { log.Printf("Intercepted expired payment registration. Failing payment. payment hash: %x, valid until: %s", paymentHash, params.ValidUntil) return InterceptResult{ Action: INTERCEPT_FAIL_HTLC_WITH_CODE, @@ -281,8 +281,8 @@ func (i *Interceptor) Intercept(nextHop string, reqPaymentHash []byte, reqOutgoi return resp.(InterceptResult) } -func (i *Interceptor) isCurrentChainFeeCheaper(params *OpeningFeeParams) bool { - settings, err := i.store.GetFeeParamsSettings() +func (i *Interceptor) isCurrentChainFeeCheaper(token string, params *OpeningFeeParams) bool { + settings, err := i.store.GetFeeParamsSettings(token) if err != nil { log.Printf("Failed to get fee params settings: %v", err) return false diff --git a/interceptor/store.go b/interceptor/store.go index 605f9cd..6ecd33f 100644 --- a/interceptor/store.go +++ b/interceptor/store.go @@ -20,9 +20,9 @@ type OpeningFeeParams struct { } type InterceptStore interface { - PaymentInfo(htlcPaymentHash []byte) (*OpeningFeeParams, []byte, []byte, []byte, int64, int64, *wire.OutPoint, error) + PaymentInfo(htlcPaymentHash []byte) (string, *OpeningFeeParams, []byte, []byte, []byte, int64, int64, *wire.OutPoint, error) SetFundingTx(paymentHash []byte, channelPoint *wire.OutPoint) error - RegisterPayment(params *OpeningFeeParams, destination, paymentHash, paymentSecret []byte, incomingAmountMsat, outgoingAmountMsat int64, tag string) error + RegisterPayment(token string, params *OpeningFeeParams, destination, paymentHash, paymentSecret []byte, incomingAmountMsat, outgoingAmountMsat int64, tag string) error InsertChannel(initialChanID, confirmedChanId uint64, channelPoint string, nodeID []byte, lastUpdate time.Time) error - GetFeeParamsSettings() ([]*OpeningFeeParamsSetting, error) + GetFeeParamsSettings(token string) ([]*OpeningFeeParamsSetting, error) } diff --git a/postgresql/intercept_store.go b/postgresql/intercept_store.go index 05fe591..941451a 100644 --- a/postgresql/intercept_store.go +++ b/postgresql/intercept_store.go @@ -15,6 +15,11 @@ import ( "github.com/jackc/pgx/v4/pgxpool" ) +type extendedParams struct { + Token string `json:"token"` + Params interceptor.OpeningFeeParams `json:"fees_params"` +} + type PostgresInterceptStore struct { pool *pgxpool.Pool } @@ -23,7 +28,7 @@ func NewPostgresInterceptStore(pool *pgxpool.Pool) *PostgresInterceptStore { return &PostgresInterceptStore{pool: pool} } -func (s *PostgresInterceptStore) PaymentInfo(htlcPaymentHash []byte) (*interceptor.OpeningFeeParams, []byte, []byte, []byte, int64, int64, *wire.OutPoint, error) { +func (s *PostgresInterceptStore) PaymentInfo(htlcPaymentHash []byte) (string, *interceptor.OpeningFeeParams, []byte, []byte, []byte, int64, int64, *wire.OutPoint, error) { var ( p *string paymentHash, paymentSecret, destination []byte @@ -40,7 +45,7 @@ func (s *PostgresInterceptStore) PaymentInfo(htlcPaymentHash []byte) (*intercept if err == pgx.ErrNoRows { err = nil } - return nil, nil, nil, nil, 0, 0, nil, err + return "", nil, nil, nil, nil, 0, 0, nil, err } var cp *wire.OutPoint @@ -51,15 +56,15 @@ func (s *PostgresInterceptStore) PaymentInfo(htlcPaymentHash []byte) (*intercept } } - var params *interceptor.OpeningFeeParams + var extParams *extendedParams if p != nil { - err = json.Unmarshal([]byte(*p), ¶ms) + err = json.Unmarshal([]byte(*p), &extParams) if err != nil { log.Printf("Failed to unmarshal OpeningFeeParams '%s': %v", *p, err) - return nil, nil, nil, nil, 0, 0, nil, err + return "", nil, nil, nil, nil, 0, 0, nil, err } } - return params, paymentHash, paymentSecret, destination, incomingAmountMsat, outgoingAmountMsat, cp, nil + return extParams.Token, &extParams.Params, paymentHash, paymentSecret, destination, incomingAmountMsat, outgoingAmountMsat, cp, nil } func (s *PostgresInterceptStore) SetFundingTx(paymentHash []byte, channelPoint *wire.OutPoint) error { @@ -72,16 +77,20 @@ func (s *PostgresInterceptStore) SetFundingTx(paymentHash []byte, channelPoint * return err } -func (s *PostgresInterceptStore) RegisterPayment(params *interceptor.OpeningFeeParams, destination, paymentHash, paymentSecret []byte, incomingAmountMsat, outgoingAmountMsat int64, tag string) error { +func (s *PostgresInterceptStore) RegisterPayment(token string, params *interceptor.OpeningFeeParams, destination, paymentHash, paymentSecret []byte, incomingAmountMsat, outgoingAmountMsat int64, tag string) error { var t *string if tag != "" { t = &tag } - p, err := json.Marshal(params) - if err != nil { - log.Printf("Failed to marshal OpeningFeeParams: %v", err) - return err + p := []byte{} + if params != nil { + var err error + p, err = json.Marshal(extendedParams{Token: token, Params: *params}) + if err != nil { + log.Printf("Failed to marshal OpeningFeeParams: %v", err) + return err + } } commandTag, err := s.pool.Exec(context.Background(), @@ -119,10 +128,10 @@ func (s *PostgresInterceptStore) InsertChannel(initialChanID, confirmedChanId ui return nil } -func (s *PostgresInterceptStore) GetFeeParamsSettings() ([]*interceptor.OpeningFeeParamsSetting, error) { - rows, err := s.pool.Query(context.Background(), `SELECT validity, params FROM new_channel_params`) +func (s *PostgresInterceptStore) GetFeeParamsSettings(token string) ([]*interceptor.OpeningFeeParamsSetting, error) { + rows, err := s.pool.Query(context.Background(), `SELECT validity, params FROM new_channel_params WHERE token=$1`, token) if err != nil { - log.Printf("GetFeeParamsSettings() error: %v", err) + log.Printf("GetFeeParamsSettings(%v) error: %v", token, err) return nil, err } diff --git a/postgresql/migrations/000012_new_channel_params_token.down.sql b/postgresql/migrations/000012_new_channel_params_token.down.sql new file mode 100644 index 0000000..0286c25 --- /dev/null +++ b/postgresql/migrations/000012_new_channel_params_token.down.sql @@ -0,0 +1,2 @@ +ALTER TABLE public.new_channel_params +DROP COLUMN token; \ No newline at end of file diff --git a/postgresql/migrations/000012_new_channel_params_token.up.sql b/postgresql/migrations/000012_new_channel_params_token.up.sql new file mode 100644 index 0000000..7bbab49 --- /dev/null +++ b/postgresql/migrations/000012_new_channel_params_token.up.sql @@ -0,0 +1 @@ +ALTER TABLE public.new_channel_params ADD token varchar; \ No newline at end of file diff --git a/server.go b/server.go index de10bd2..db35c4a 100644 --- a/server.go +++ b/server.go @@ -58,13 +58,15 @@ type node struct { openChannelReqGroup singleflight.Group } +type contextKey string + func (s *server) ChannelInformation(ctx context.Context, in *lspdrpc.ChannelInformationRequest) (*lspdrpc.ChannelInformationReply, error) { - node, err := getNode(ctx) + node, token, err := s.getNode(ctx) if err != nil { return nil, err } - params, err := s.createOpeningParamsMenu(ctx, node) + params, err := s.createOpeningParamsMenu(ctx, node, token) if err != nil { return nil, err } @@ -90,10 +92,11 @@ func (s *server) ChannelInformation(ctx context.Context, in *lspdrpc.ChannelInfo func (s *server) createOpeningParamsMenu( ctx context.Context, node *node, + token string, ) ([]*lspdrpc.OpeningFeeParams, error) { var menu []*lspdrpc.OpeningFeeParams - settings, err := s.store.GetFeeParamsSettings() + settings, err := s.store.GetFeeParamsSettings(token) if err != nil { log.Printf("Failed to fetch fee params settings: %v", err) return nil, fmt.Errorf("failed to get opening_fee_params") @@ -208,7 +211,7 @@ func (s *server) RegisterPayment( ctx context.Context, in *lspdrpc.RegisterPaymentRequest, ) (*lspdrpc.RegisterPaymentReply, error) { - node, err := getNode(ctx) + node, token, err := s.getNode(ctx) if err != nil { return nil, err } @@ -275,7 +278,7 @@ func (s *server) RegisterPayment( MaxClientToSelfDelay: pi.OpeningFeeParams.MaxClientToSelfDelay, Promise: pi.OpeningFeeParams.Promise, } - err = s.store.RegisterPayment(params, pi.Destination, pi.PaymentHash, pi.PaymentSecret, pi.IncomingAmountMsat, pi.OutgoingAmountMsat, pi.Tag) + err = s.store.RegisterPayment(token, params, pi.Destination, pi.PaymentHash, pi.PaymentSecret, pi.IncomingAmountMsat, pi.OutgoingAmountMsat, pi.Tag) if err != nil { log.Printf("RegisterPayment() error: %v", err) return nil, fmt.Errorf("RegisterPayment() error: %w", err) @@ -284,7 +287,7 @@ func (s *server) RegisterPayment( } func (s *server) OpenChannel(ctx context.Context, in *lspdrpc.OpenChannelRequest) (*lspdrpc.OpenChannelReply, error) { - node, err := getNode(ctx) + node, _, err := s.getNode(ctx) if err != nil { return nil, err } @@ -368,7 +371,7 @@ func (n *node) getSignedEncryptedData(in *lspdrpc.Encrypted) (string, []byte, bo } func (s *server) CheckChannels(ctx context.Context, in *lspdrpc.Encrypted) (*lspdrpc.Encrypted, error) { - node, err := getNode(ctx) + node, _, err := s.getNode(ctx) if err != nil { return nil, err } @@ -473,12 +476,14 @@ func NewGrpcServer( } } - _, exists := nodes[config.Token] - if exists { - return nil, fmt.Errorf("cannot have multiple nodes with the same token") - } + for _, token := range config.Tokens { + _, exists := nodes[token] + if exists { + return nil, fmt.Errorf("cannot have multiple nodes with the same token") + } - nodes[config.Token] = node + nodes[token] = node + } } return &server{ @@ -534,12 +539,12 @@ func (s *server) Start() error { } token := strings.Replace(auth, "Bearer ", "", 1) - node, ok := s.nodes[token] + _, ok := s.nodes[token] if !ok { continue } - return handler(context.WithValue(ctx, "node", node), req) + return handler(context.WithValue(ctx, contextKey("token"), token), req) } } return nil, status.Errorf(codes.PermissionDenied, "Not authorized") @@ -563,18 +568,21 @@ func (s *server) Stop() { } } -func getNode(ctx context.Context) (*node, error) { - n := ctx.Value("node") - if n == nil { - return nil, status.Errorf(codes.PermissionDenied, "Not authorized") +func (s *server) getNode(ctx context.Context) (*node, string, error) { + tok := ctx.Value(contextKey("token")) + if tok == nil { + return nil, "", status.Errorf(codes.PermissionDenied, "Not authorized") } - node, ok := n.(*node) - if !ok || node == nil { - return nil, status.Errorf(codes.PermissionDenied, "Not authorized") + token, ok := tok.(string) + if !ok { + return nil, "", status.Errorf(codes.PermissionDenied, "Not authorized") } - - return node, nil + node, ok := s.nodes[token] + if !ok { + return nil, "", status.Errorf(codes.PermissionDenied, "Not authorized") + } + return node, token, nil } func checkPayment(params *lspdrpc.OpeningFeeParams, incomingAmountMsat, outgoingAmountMsat int64) error { From 0bbf6638b4e2042f672f3af3f250dc3239e1cd48 Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Mon, 12 Jun 2023 13:47:01 +0200 Subject: [PATCH 186/214] fix broken integration tests due to token filter --- itest/lspd_node.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/itest/lspd_node.go b/itest/lspd_node.go index 1f49398..cd926de 100644 --- a/itest/lspd_node.go +++ b/itest/lspd_node.go @@ -96,7 +96,7 @@ func newLspd(h *lntest.TestHarness, mem *mempoolApi, name string, nodeConfig *co conf := &config.NodeConfig{ Name: name, LspdPrivateKey: hex.EncodeToString(lspdPrivateKeyBytes), - Token: "hello", + Tokens: []string{"hello"}, Host: "host:port", PublicChannelAmount: 1000183, ChannelAmount: 100000, @@ -202,10 +202,10 @@ func (l *lspBase) Initialize() error { _, err = pgxPool.Exec( l.harness.Ctx, - `INSERT INTO new_channel_params (validity, params) + `INSERT INTO new_channel_params (validity, params, token) VALUES - (3600, '{"min_msat": "1000000", "proportional": 7500, "max_idle_time": 4320, "max_client_to_self_delay": 432}'), - (259200, '{"min_msat": "1100000", "proportional": 7500, "max_idle_time": 4320, "max_client_to_self_delay": 432}');`, + (3600, '{"min_msat": "1000000", "proportional": 7500, "max_idle_time": 4320, "max_client_to_self_delay": 432}', 'hello'), + (259200, '{"min_msat": "1100000", "proportional": 7500, "max_idle_time": 4320, "max_client_to_self_delay": 432}', 'hello');`, ) if err != nil { lntest.PerformCleanup(cleanups) @@ -306,7 +306,7 @@ func SetFeeParams(l LspNode, settings []*FeeParamSetting) error { return nil } - query := `INSERT INTO new_channel_params (validity, params) VALUES ` + query := `INSERT INTO new_channel_params (validity, params, token) VALUES ` first := true for _, setting := range settings { if !first { @@ -314,7 +314,7 @@ func SetFeeParams(l LspNode, settings []*FeeParamSetting) error { } query += fmt.Sprintf( - `(%d, '{"min_msat": "%d", "proportional": %d, "max_idle_time": 4320, "max_client_to_self_delay": 432}')`, + `(%d, '{"min_msat": "%d", "proportional": %d, "max_idle_time": 4320, "max_client_to_self_delay": 432}', 'hello')`, int64(setting.Validity.Seconds()), setting.MinMsat, setting.Proportional, From 37f6740348dedd36751cd873e30e06542f922e33 Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Mon, 12 Jun 2023 15:53:18 +0200 Subject: [PATCH 187/214] update tests to work with cln v23.05 --- go.mod | 2 +- itest/cln_lspd_node.go | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 115e08b..be5c76e 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.19 require ( github.com/aws/aws-sdk-go v1.34.0 - github.com/breez/lntest v0.0.20 + github.com/breez/lntest v0.0.21 github.com/btcsuite/btcd v0.23.3 github.com/btcsuite/btcd/btcec/v2 v2.2.1 github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 diff --git a/itest/cln_lspd_node.go b/itest/cln_lspd_node.go index 30a9f7e..e410201 100644 --- a/itest/cln_lspd_node.go +++ b/itest/cln_lspd_node.go @@ -59,6 +59,7 @@ func NewClnLspdNode(h *lntest.TestHarness, m *lntest.Miner, mem *mempoolApi, nam fmt.Sprintf("--cltv-delta=%d", lspCltvDelta), "--max-concurrent-htlcs=30", "--dev-allowdustreserve=true", + "--allow-deprecated-apis=true", } lightningNode := lntest.NewClnNode(h, m, name, args...) cln := &config.ClnConfig{ From e59378a491952f434cb5b88d99b24d5a3253aff1 Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Mon, 12 Jun 2023 16:15:57 +0200 Subject: [PATCH 188/214] tidy up logging --- cln/cln_interceptor.go | 13 ------------- interceptor/intercept.go | 11 +++++------ lnd/interceptor.go | 13 ------------- 3 files changed, 5 insertions(+), 32 deletions(-) diff --git a/cln/cln_interceptor.go b/cln/cln_interceptor.go index 0efa6a8..a2c6162 100644 --- a/cln/cln_interceptor.go +++ b/cln/cln_interceptor.go @@ -144,19 +144,6 @@ func (i *ClnHtlcInterceptor) intercept() error { } } - log.Printf("correlationid: %v\nhtlc: %v\nchanID: %v\nnextHop: %v\nincoming amount: %v\noutgoing amount: %v\nincoming expiry: %v\noutgoing expiry: %v\npaymentHash: %v\nonionBlob: %v\n\n", - request.Correlationid, - request.Htlc, - request.Onion.ShortChannelId, - nextHop, - request.Htlc.AmountMsat, //with fees - request.Onion.ForwardMsat, - request.Htlc.CltvExpiryRelative, - request.Htlc.CltvExpiry, - request.Htlc.PaymentHash, - request, - ) - i.doneWg.Add(1) go func() { paymentHash, err := hex.DecodeString(request.Htlc.PaymentHash) diff --git a/interceptor/intercept.go b/interceptor/intercept.go index 2f851b7..ae7545d 100644 --- a/interceptor/intercept.go +++ b/interceptor/intercept.go @@ -84,8 +84,7 @@ func (i *Interceptor) Intercept(nextHop string, reqPaymentHash []byte, reqOutgoi FailureCode: FAILURE_TEMPORARY_NODE_FAILURE, }, nil } - log.Printf("paymentHash:%x\npaymentSecret:%x\ndestination:%x\nincomingAmountMsat:%v\noutgoingAmountMsat:%v", - paymentHash, paymentSecret, destination, incomingAmountMsat, outgoingAmountMsat) + if paymentSecret == nil || (nextHop != "" && nextHop != hex.EncodeToString(destination)) { return InterceptResult{ Action: INTERCEPT_RESUME, @@ -96,7 +95,7 @@ func (i *Interceptor) Intercept(nextHop string, reqPaymentHash []byte, reqOutgoi if bytes.Equal(paymentHash, reqPaymentHash) { // TODO: When opening_fee_params is enforced, turn this check in a temporary channel failure. if params == nil { - log.Printf("DEPRECATED: Intercepted htlc with deprecated fee mechanism.") + log.Printf("DEPRECATED: Intercepted htlc with deprecated fee mechanism. Using default fees. payment hash: %s", reqPaymentHashStr) params = &OpeningFeeParams{ MinMsat: uint64(i.config.ChannelMinimumFeeMsat), Proportional: uint32(i.config.ChannelFeePermyriad * 100), @@ -131,7 +130,7 @@ func (i *Interceptor) Intercept(nextHop string, reqPaymentHash []byte, reqOutgoi }, nil } - log.Printf("Intercepted expired payment registration. Opening channel anyway, because it's cheaper at the current rate. %+v", params) + log.Printf("Intercepted expired payment registration. Opening channel anyway, because it's cheaper at the current rate. paymenthash: %s, params: %+v", reqPaymentHashStr, params) } channelPoint, err = i.openChannel(reqPaymentHash, destination, incomingAmountMsat, tag) @@ -230,7 +229,7 @@ func (i *Interceptor) Intercept(nextHop string, reqPaymentHash []byte, reqOutgoi for { chanResult, _ := i.client.GetChannel(destination, *channelPoint) if chanResult != nil { - log.Printf("channel opended successfully alias: %v, confirmed: %v", chanResult.InitialChannelID.ToString(), chanResult.ConfirmedChannelID.ToString()) + log.Printf("channel opened successfully alias: %v, confirmed: %v", chanResult.InitialChannelID.ToString(), chanResult.ConfirmedChannelID.ToString()) err := i.store.InsertChannel( uint64(chanResult.InitialChannelID), @@ -271,7 +270,7 @@ func (i *Interceptor) Intercept(nextHop string, reqPaymentHash []byte, reqOutgoi <-time.After(1 * time.Second) } - log.Printf("Error: Channel failed to opened... timed out. ") + log.Printf("Error: Channel failed to open... timed out. ") return InterceptResult{ Action: INTERCEPT_FAIL_HTLC_WITH_CODE, FailureCode: FAILURE_TEMPORARY_CHANNEL_FAILURE, diff --git a/lnd/interceptor.go b/lnd/interceptor.go index 7affba9..925a6eb 100644 --- a/lnd/interceptor.go +++ b/lnd/interceptor.go @@ -2,7 +2,6 @@ package lnd import ( "context" - "fmt" "log" "sync" "time" @@ -138,18 +137,6 @@ func (i *LndHtlcInterceptor) intercept() error { } } - fmt.Printf("htlc: %v\nchanID: %v\nnextHop: %v\nincoming amount: %v\noutgoing amount: %v\nincomin expiry: %v\noutgoing expiry: %v\npaymentHash: %x\nonionBlob: %x\n\n", - request.IncomingCircuitKey.HtlcId, - request.IncomingCircuitKey.ChanId, - nextHop, - request.IncomingAmountMsat, - request.OutgoingAmountMsat, - request.IncomingExpiry, - request.OutgoingExpiry, - request.PaymentHash, - request.OnionBlob, - ) - i.doneWg.Add(1) go func() { interceptResult := i.interceptor.Intercept(nextHop, request.PaymentHash, request.OutgoingAmountMsat, request.OutgoingExpiry, request.IncomingExpiry) From db3f8999ac60cb78bf166947c1cb89fb82859ecf Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Thu, 15 Jun 2023 10:14:59 +0200 Subject: [PATCH 189/214] update used versions in the readme --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index f4712bd..15584ea 100644 --- a/README.md +++ b/README.md @@ -56,9 +56,9 @@ The lsp supports probing non-mpp payments if the payment hash for probing is sha In order to run the integration tests, you need: - Docker running - python3 installed -- A development build of lightningd v22.11 -- lnd v0.15.4 lsp version https://github.com/breez/lnd/commit/6ee6b89e3a4e3f2776643a75a9100d13a2090725 -- lnd v0.15.3 breez client version https://github.com/breez/lnd/commit/e1570b327b5de52d03817ad516d0bdfa71797c64 +- A development build of lightningd v23.05.1 +- lnd v0.16.2 lsp version https://github.com/breez/lnd/commit/cebcdf1b17fdedf7d69207d98c31cf8c3b257531 +- lnd v0.16.2 breez client version https://github.com/breez/lnd/commit/9d744cd396af707d77473d58c97947b8e0a25d08 - bitcoind (tested with v23.0) - bitcoin-cli (tested with v23.0) - build of lspd (go build .) From 62890978748304519d8e82b83be8dfed5ef577bc Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Thu, 15 Jun 2023 10:15:13 +0200 Subject: [PATCH 190/214] add a reference to the genkey command --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 15584ea..b91f589 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ This is a simple example of an lspd that works with an [lnd](https://github.com/ 1. Compile lspd using `go build .` ### Before running -1. Create a random token (for instance using the command `openssl rand -base64 48`) +1. Create a random token (for instance using the command `openssl rand -base64 48`, or `./lspd genkey`) 1. Define the environment variables as described in sample.env. If `CERTMAGIC_DOMAIN` is defined, certificate for this domain is automatically obtained and renewed from Let's Encrypt. In this case, the port needs to be 443. If `CERTMAGIC_DOMAIN` is not defined, lspd needs to run behind a reverse proxy like treafik or nginx. ### Running lspd on LND From 748e7c5b2973deee03e1c958ba6d0d749280b8a5 Mon Sep 17 00:00:00 2001 From: Yaacov Akiba Slama Date: Fri, 30 Jun 2023 08:15:42 +0300 Subject: [PATCH 191/214] Use '-' instead of '.' in option name --- cln_plugin/cln_plugin.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cln_plugin/cln_plugin.go b/cln_plugin/cln_plugin.go index 4ae8b4b..7bbfe86 100644 --- a/cln_plugin/cln_plugin.go +++ b/cln_plugin/cln_plugin.go @@ -17,8 +17,8 @@ import ( ) const ( - SubscriberTimeoutOption = "lsp.subscribertimeout" - ListenAddressOption = "lsp.listen" + SubscriberTimeoutOption = "lsp-subscribertimeout" + ListenAddressOption = "lsp-listen" ) var ( From 1b5356d4bf0565139e9e33bfb22d36b21b2c90ea Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Fri, 30 Jun 2023 16:35:43 +0200 Subject: [PATCH 192/214] fix changed cln option names in itests --- itest/cln_breez_client.go | 2 +- itest/cln_lspd_node.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/itest/cln_breez_client.go b/itest/cln_breez_client.go index 7cf1911..369ba3b 100644 --- a/itest/cln_breez_client.go +++ b/itest/cln_breez_client.go @@ -78,7 +78,7 @@ func newClnBreezClient(h *lntest.TestHarness, m *lntest.Miner, name string) Bree name, fmt.Sprintf("--plugin=%s", pluginFilePath), fmt.Sprintf("--plugin=%s", *clnPluginExec), - fmt.Sprintf("--lsp.listen=%s", htlcAcceptorAddress), + fmt.Sprintf("--lsp-listen=%s", htlcAcceptorAddress), // NOTE: max-concurrent-htlcs is 30 on mainnet by default. In cln V22.11 // there is a check for 'all dust' commitment transactions. The max // concurrent HTLCs of both sides of the channel * dust limit must be diff --git a/itest/cln_lspd_node.go b/itest/cln_lspd_node.go index e410201..f5f94bc 100644 --- a/itest/cln_lspd_node.go +++ b/itest/cln_lspd_node.go @@ -53,7 +53,7 @@ func NewClnLspdNode(h *lntest.TestHarness, m *lntest.Miner, mem *mempoolApi, nam args := []string{ fmt.Sprintf("--plugin=%s", pluginBinary), - fmt.Sprintf("--lsp.listen=%s", pluginAddress), + fmt.Sprintf("--lsp-listen=%s", pluginAddress), fmt.Sprintf("--fee-base=%d", lspBaseFeeMsat), fmt.Sprintf("--fee-per-satoshi=%d", lspFeeRatePpm), fmt.Sprintf("--cltv-delta=%d", lspCltvDelta), From a6c437e4fdb7053f8c386d31b90b7818159afd52 Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Thu, 15 Jun 2023 10:49:25 +0200 Subject: [PATCH 193/214] upgrade to LND 0.16.2 dependency --- go.mod | 93 +++++++++++++++++++++++++++++++++------------------------- 1 file changed, 53 insertions(+), 40 deletions(-) diff --git a/go.mod b/go.mod index be5c76e..daa6930 100644 --- a/go.mod +++ b/go.mod @@ -5,9 +5,9 @@ go 1.19 require ( github.com/aws/aws-sdk-go v1.34.0 github.com/breez/lntest v0.0.21 - github.com/btcsuite/btcd v0.23.3 - github.com/btcsuite/btcd/btcec/v2 v2.2.1 - github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 + github.com/btcsuite/btcd v0.23.5-0.20230228185050-38331963bddd + github.com/btcsuite/btcd/btcec/v2 v2.3.2 + github.com/btcsuite/btcd/chaincfg/chainhash v1.0.2 github.com/caddyserver/certmagic v0.11.2 github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 github.com/docker/docker v20.10.24+incompatible @@ -16,13 +16,13 @@ require ( github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 github.com/jackc/pgtype v1.8.1 github.com/jackc/pgx/v4 v4.13.0 - github.com/lightningnetwork/lightning-onion v1.2.0 - github.com/lightningnetwork/lnd v0.15.4-beta - github.com/lightningnetwork/lnd/tlv v1.0.3 + github.com/lightningnetwork/lightning-onion v1.2.1-0.20221202012345-ca23184850a1 + github.com/lightningnetwork/lnd v0.16.2-beta + github.com/lightningnetwork/lnd/tlv v1.1.0 github.com/niftynei/glightning v0.8.2 github.com/stretchr/testify v1.8.1 - golang.org/x/exp v0.0.0-20221212164502-fae10dda9338 - golang.org/x/sync v0.0.0-20210220032951-036812b2e83c + golang.org/x/exp v0.0.0-20230321023759-10a507213a29 + golang.org/x/sync v0.1.0 google.golang.org/grpc v1.50.1 ) @@ -32,14 +32,34 @@ require ( github.com/docker/distribution v2.8.1+incompatible // indirect github.com/docker/go-units v0.5.0 // indirect github.com/ethereum/go-ethereum v1.10.17 // indirect + github.com/golang-jwt/jwt/v4 v4.4.2 // indirect + github.com/google/uuid v1.3.0 // indirect + github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect + github.com/lightninglabs/neutrino/cache v1.1.1 // indirect + github.com/mattn/go-isatty v0.0.16 // indirect github.com/moby/term v0.0.0-20221120202655-abb19827d345 // indirect github.com/morikuni/aec v1.0.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.0.2 // indirect github.com/pkg/errors v0.9.1 // indirect + github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect gitlab.com/yawning/bsaes.git v0.0.0-20190805113838-0a714cd429ec // indirect - golang.org/x/net v0.7.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.0.1 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.0.1 // indirect + golang.org/x/mod v0.8.0 // indirect + golang.org/x/net v0.8.0 // indirect + golang.org/x/tools v0.6.0 // indirect gotest.tools/v3 v3.4.0 // indirect + lukechampine.com/uint128 v1.2.0 // indirect + modernc.org/cc/v3 v3.40.0 // indirect + modernc.org/ccgo/v3 v3.16.13 // indirect + modernc.org/libc v1.22.2 // indirect + modernc.org/mathutil v1.5.0 // indirect + modernc.org/memory v1.4.0 // indirect + modernc.org/opt v0.1.3 // indirect + modernc.org/sqlite v1.20.3 // indirect + modernc.org/strutil v1.1.3 // indirect + modernc.org/token v1.0.1 // indirect ) require ( @@ -47,10 +67,10 @@ require ( github.com/aead/siphash v1.0.1 // indirect github.com/andybalholm/brotli v1.0.3 // indirect github.com/beorn7/perks v1.0.1 // indirect - github.com/btcsuite/btcd/btcutil v1.1.2 // indirect - github.com/btcsuite/btcd/btcutil/psbt v1.1.5 // indirect + github.com/btcsuite/btcd/btcutil v1.1.3 // indirect + github.com/btcsuite/btcd/btcutil/psbt v1.1.8 // indirect github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f // indirect - github.com/btcsuite/btcwallet v0.16.1 // indirect + github.com/btcsuite/btcwallet v0.16.9 // indirect github.com/btcsuite/btcwallet/wallet/txauthor v1.3.2 // indirect github.com/btcsuite/btcwallet/wallet/txrules v1.2.0 // indirect github.com/btcsuite/btcwallet/wallet/txsizes v1.2.3 // indirect @@ -59,7 +79,7 @@ require ( github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd // indirect github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 // indirect github.com/btcsuite/winsvc v1.0.0 // indirect - github.com/cenkalti/backoff/v4 v4.0.0 // indirect + github.com/cenkalti/backoff/v4 v4.1.1 // indirect github.com/cespare/xxhash/v2 v2.1.1 // indirect github.com/coreos/go-semver v0.3.0 // indirect github.com/coreos/go-systemd/v22 v22.3.2 // indirect @@ -68,10 +88,8 @@ require ( github.com/decred/dcrd/lru v1.0.0 // indirect github.com/dsnet/compress v0.0.1 // indirect github.com/dustin/go-humanize v1.0.0 // indirect - github.com/dvyukov/go-fuzz v0.0.0-20210602112143-b1f3d6f4ef4e // indirect github.com/ecies/go/v2 v2.0.4 github.com/fergusstrange/embedded-postgres v1.10.0 // indirect - github.com/form3tech-oss/jwt-go v3.2.3+incompatible // indirect github.com/go-acme/lego/v3 v3.7.0 // indirect github.com/go-errors/errors v1.0.1 // indirect github.com/gofrs/uuid v4.2.0+incompatible // indirect @@ -105,13 +123,13 @@ require ( github.com/kr/pretty v0.3.0 // indirect github.com/lib/pq v1.10.3 // indirect github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf // indirect - github.com/lightninglabs/neutrino v0.14.2 // indirect + github.com/lightninglabs/neutrino v0.15.0 // indirect github.com/lightningnetwork/lnd/clock v1.1.0 // indirect github.com/lightningnetwork/lnd/healthcheck v1.2.2 // indirect - github.com/lightningnetwork/lnd/kvdb v1.3.1 // indirect + github.com/lightningnetwork/lnd/kvdb v1.4.1 // indirect github.com/lightningnetwork/lnd/queue v1.1.0 // indirect github.com/lightningnetwork/lnd/ticker v1.1.0 // indirect - github.com/lightningnetwork/lnd/tor v1.0.1 // indirect + github.com/lightningnetwork/lnd/tor v1.1.0 // indirect github.com/ltcsuite/ltcd v0.0.0-20190101042124-f37f8bf35796 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect github.com/mholt/archiver/v3 v3.5.0 // indirect @@ -137,30 +155,25 @@ require ( github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect go.etcd.io/bbolt v1.3.6 // indirect - go.etcd.io/etcd/api/v3 v3.5.0 // indirect - go.etcd.io/etcd/client/pkg/v3 v3.5.0 // indirect - go.etcd.io/etcd/client/v2 v2.305.0 // indirect - go.etcd.io/etcd/client/v3 v3.5.0 // indirect - go.etcd.io/etcd/pkg/v3 v3.5.0 // indirect - go.etcd.io/etcd/raft/v3 v3.5.0 // indirect - go.etcd.io/etcd/server/v3 v3.5.0 // indirect - go.opentelemetry.io/contrib v0.20.0 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.20.0 // indirect - go.opentelemetry.io/otel v0.20.0 // indirect - go.opentelemetry.io/otel/exporters/otlp v0.20.0 // indirect - go.opentelemetry.io/otel/metric v0.20.0 // indirect - go.opentelemetry.io/otel/sdk v0.20.0 // indirect - go.opentelemetry.io/otel/sdk/export/metric v0.20.0 // indirect - go.opentelemetry.io/otel/sdk/metric v0.20.0 // indirect - go.opentelemetry.io/otel/trace v0.20.0 // indirect - go.opentelemetry.io/proto/otlp v0.7.0 // indirect + go.etcd.io/etcd/api/v3 v3.5.7 // indirect + go.etcd.io/etcd/client/pkg/v3 v3.5.7 // indirect + go.etcd.io/etcd/client/v2 v2.305.7 // indirect + go.etcd.io/etcd/client/v3 v3.5.7 // indirect + go.etcd.io/etcd/pkg/v3 v3.5.7 // indirect + go.etcd.io/etcd/raft/v3 v3.5.7 // indirect + go.etcd.io/etcd/server/v3 v3.5.7 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.25.0 // indirect + go.opentelemetry.io/otel v1.0.1 // indirect + go.opentelemetry.io/otel/sdk v1.0.1 // indirect + go.opentelemetry.io/otel/trace v1.0.1 // indirect + go.opentelemetry.io/proto/otlp v0.9.0 // indirect go.uber.org/atomic v1.7.0 // indirect go.uber.org/multierr v1.8.0 // indirect go.uber.org/zap v1.17.0 // indirect - golang.org/x/crypto v0.1.0 // indirect - golang.org/x/sys v0.5.0 // indirect - golang.org/x/term v0.5.0 // indirect - golang.org/x/text v0.7.0 // indirect + golang.org/x/crypto v0.7.0 // indirect + golang.org/x/sys v0.6.0 // indirect + golang.org/x/term v0.6.0 // indirect + golang.org/x/text v0.8.0 // indirect golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba // indirect google.golang.org/genproto v0.0.0-20210617175327-b9e0b3197ced // indirect google.golang.org/protobuf v1.27.1 @@ -174,6 +187,6 @@ require ( sigs.k8s.io/yaml v1.2.0 // indirect ) -replace github.com/lightningnetwork/lnd v0.15.4-beta => github.com/breez/lnd v0.15.0-beta.rc6.0.20220831104847-00b86a81e57a +replace github.com/lightningnetwork/lnd v0.16.2-beta => github.com/breez/lnd v0.15.0-beta.rc6.0.20230501134702-cebcdf1b17fd replace github.com/niftynei/glightning v0.8.2 => github.com/breez/glightning v0.0.0-20221219103549-0e2a13b9b3ed From 898b69f9a7242e3083b77455f5ed107a4e27442b Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Thu, 15 Jun 2023 12:10:02 +0200 Subject: [PATCH 194/214] split channel opener server from grpc server --- server.go => channel_opener_server.go | 202 +++----------------------- grpc_server.go | 196 +++++++++++++++++++++++++ main.go | 3 +- 3 files changed, 217 insertions(+), 184 deletions(-) rename server.go => channel_opener_server.go (72%) create mode 100644 grpc_server.go diff --git a/server.go b/channel_opener_server.go similarity index 72% rename from server.go rename to channel_opener_server.go index db35c4a..56d2bbb 100644 --- a/server.go +++ b/channel_opener_server.go @@ -3,64 +3,46 @@ package main import ( "context" "crypto/sha256" - "crypto/tls" "encoding/hex" "encoding/json" "fmt" "log" - "net" "sort" - "strings" "time" "github.com/breez/lspd/basetypes" "github.com/breez/lspd/btceclegacy" - "github.com/breez/lspd/cln" - "github.com/breez/lspd/config" "github.com/breez/lspd/interceptor" "github.com/breez/lspd/lightning" - "github.com/breez/lspd/lnd" lspdrpc "github.com/breez/lspd/rpc" ecies "github.com/ecies/go/v2" "github.com/golang/protobuf/proto" - 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" "google.golang.org/grpc/status" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/ecdsa" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" - "github.com/caddyserver/certmagic" "github.com/lightningnetwork/lnd/lnwire" ) -type server struct { +type channelOpenerServer struct { lspdrpc.ChannelOpenerServer - address string - certmagicDomain string - lis net.Listener - s *grpc.Server - nodes map[string]*node - store interceptor.InterceptStore + store interceptor.InterceptStore } -type node struct { - client lightning.Client - nodeConfig *config.NodeConfig - privateKey *btcec.PrivateKey - publicKey *btcec.PublicKey - eciesPrivateKey *ecies.PrivateKey - eciesPublicKey *ecies.PublicKey - openChannelReqGroup singleflight.Group +func NewChannelOpenerServer( + store interceptor.InterceptStore, +) *channelOpenerServer { + return &channelOpenerServer{ + store: store, + } } type contextKey string -func (s *server) ChannelInformation(ctx context.Context, in *lspdrpc.ChannelInformationRequest) (*lspdrpc.ChannelInformationReply, error) { +func (s *channelOpenerServer) ChannelInformation(ctx context.Context, in *lspdrpc.ChannelInformationRequest) (*lspdrpc.ChannelInformationReply, error) { node, token, err := s.getNode(ctx) if err != nil { return nil, err @@ -89,7 +71,7 @@ func (s *server) ChannelInformation(ctx context.Context, in *lspdrpc.ChannelInfo }, nil } -func (s *server) createOpeningParamsMenu( +func (s *channelOpenerServer) createOpeningParamsMenu( ctx context.Context, node *node, token string, @@ -207,7 +189,7 @@ func validateOpeningFeeParams(node *node, params *lspdrpc.OpeningFeeParams) bool return true } -func (s *server) RegisterPayment( +func (s *channelOpenerServer) RegisterPayment( ctx context.Context, in *lspdrpc.RegisterPaymentRequest, ) (*lspdrpc.RegisterPaymentReply, error) { @@ -286,7 +268,7 @@ func (s *server) RegisterPayment( return &lspdrpc.RegisterPaymentReply{}, nil } -func (s *server) OpenChannel(ctx context.Context, in *lspdrpc.OpenChannelRequest) (*lspdrpc.OpenChannelReply, error) { +func (s *channelOpenerServer) OpenChannel(ctx context.Context, in *lspdrpc.OpenChannelRequest) (*lspdrpc.OpenChannelReply, error) { node, _, err := s.getNode(ctx) if err != nil { return nil, err @@ -370,7 +352,7 @@ func (n *node) getSignedEncryptedData(in *lspdrpc.Encrypted) (string, []byte, bo return hex.EncodeToString(signed.Pubkey), signed.Data, usedEcies, nil } -func (s *server) CheckChannels(ctx context.Context, in *lspdrpc.Encrypted) (*lspdrpc.Encrypted, error) { +func (s *channelOpenerServer) CheckChannels(ctx context.Context, in *lspdrpc.Encrypted) (*lspdrpc.Encrypted, error) { node, _, err := s.getNode(ctx) if err != nil { return nil, err @@ -425,164 +407,18 @@ func (s *server) CheckChannels(ctx context.Context, in *lspdrpc.Encrypted) (*lsp return &lspdrpc.Encrypted{Data: encrypted}, nil } -func NewGrpcServer( - configs []*config.NodeConfig, - address string, - certmagicDomain string, - store interceptor.InterceptStore, -) (*server, 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 &server{ - address: address, - certmagicDomain: certmagicDomain, - nodes: nodes, - store: store, - }, nil -} - -func (s *server) 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 - lis, err = net.Listen("tcp", s.address) - if err != nil { - log.Fatalf("failed to listen: %v", err) - } - } else { - tlsConfig, err := certmagic.TLS([]string{s.certmagicDomain}) - if err != nil { - log.Fatalf("failed to run certmagic: %v", err) - } - lis, err = tls.Listen("tcp", s.address, tlsConfig) - if err != nil { - log.Fatalf("failed to listen: %v", err) - } - } - - srv := 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 !strings.HasPrefix(auth, "Bearer ") { - continue - } - - token := strings.Replace(auth, "Bearer ", "", 1) - _, ok := s.nodes[token] - if !ok { - continue - } - - return handler(context.WithValue(ctx, contextKey("token"), token), req) - } - } - return nil, status.Errorf(codes.PermissionDenied, "Not authorized") - }), - ) - lspdrpc.RegisterChannelOpenerServer(srv, s) - - s.s = srv - s.lis = lis - if err := srv.Serve(lis); err != nil { - return fmt.Errorf("failed to serve: %v", err) - } - - return nil -} - -func (s *server) Stop() { - srv := s.s - if srv != nil { - srv.GracefulStop() - } -} - -func (s *server) getNode(ctx context.Context) (*node, string, error) { - tok := ctx.Value(contextKey("token")) - if tok == nil { +func (s *channelOpenerServer) getNode(ctx context.Context) (*node, string, error) { + nd := ctx.Value(contextKey("node")) + if nd == nil { return nil, "", status.Errorf(codes.PermissionDenied, "Not authorized") } - token, ok := tok.(string) + nodeContext, ok := nd.(*nodeContext) if !ok { return nil, "", status.Errorf(codes.PermissionDenied, "Not authorized") } - node, ok := s.nodes[token] - if !ok { - return nil, "", status.Errorf(codes.PermissionDenied, "Not authorized") - } - return node, token, nil + + return nodeContext.node, nodeContext.token, nil } func checkPayment(params *lspdrpc.OpeningFeeParams, incomingAmountMsat, outgoingAmountMsat int64) error { diff --git a/grpc_server.go b/grpc_server.go new file mode 100644 index 0000000..4c45bc9 --- /dev/null +++ b/grpc_server.go @@ -0,0 +1,196 @@ +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" + lspdrpc "github.com/breez/lspd/rpc" + "github.com/btcsuite/btcd/btcec/v2" + "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" + "google.golang.org/grpc/status" +) + +type grpcServer struct { + address string + certmagicDomain string + lis net.Listener + s *grpc.Server + nodes map[string]*node + c lspdrpc.ChannelOpenerServer +} + +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 +} + +func NewGrpcServer( + configs []*config.NodeConfig, + address string, + certmagicDomain string, + c lspdrpc.ChannelOpenerServer, +) (*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{ + address: address, + certmagicDomain: certmagicDomain, + nodes: nodes, + c: c, + }, 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 + lis, err = net.Listen("tcp", s.address) + if err != nil { + log.Fatalf("failed to listen: %v", err) + } + } else { + tlsConfig, err := certmagic.TLS([]string{s.certmagicDomain}) + if err != nil { + log.Fatalf("failed to run certmagic: %v", err) + } + lis, err = tls.Listen("tcp", s.address, tlsConfig) + if err != nil { + log.Fatalf("failed to listen: %v", err) + } + } + + srv := 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 !strings.HasPrefix(auth, "Bearer ") { + continue + } + + token := strings.Replace(auth, "Bearer ", "", 1) + node, ok := s.nodes[token] + if !ok { + continue + } + + return handler(context.WithValue(ctx, contextKey("node"), &nodeContext{ + token: token, + node: node, + }), req) + } + } + return nil, status.Errorf(codes.PermissionDenied, "Not authorized") + }), + ) + lspdrpc.RegisterChannelOpenerServer(srv, s.c) + + s.s = srv + s.lis = lis + if err := srv.Serve(lis); err != nil { + return fmt.Errorf("failed to serve: %v", err) + } + + return nil +} + +func (s *grpcServer) Stop() { + srv := s.s + if srv != nil { + srv.GracefulStop() + } +} diff --git a/main.go b/main.go index 2e3df49..9c20127 100644 --- a/main.go +++ b/main.go @@ -117,7 +117,8 @@ func main() { address := os.Getenv("LISTEN_ADDRESS") certMagicDomain := os.Getenv("CERTMAGIC_DOMAIN") - s, err := NewGrpcServer(nodes, address, certMagicDomain, interceptStore) + cs := NewChannelOpenerServer(interceptStore) + s, err := NewGrpcServer(nodes, address, certMagicDomain, cs) if err != nil { log.Fatalf("failed to initialize grpc server: %v", err) } From 205d39d715fddcd279e3493c79d8946395c868c5 Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Thu, 15 Jun 2023 10:50:20 +0200 Subject: [PATCH 195/214] move nexthop logic inside the interceptor --- cln/cln_client.go | 28 +++++++++++++++++++++++++++- cln/cln_interceptor.go | 26 +++++++++++--------------- interceptor/intercept.go | 21 +++++++++++++++++++-- lightning/client.go | 1 + lnd/client.go | 35 ++++++++++++++++++++++++++--------- lnd/interceptor.go | 15 +++------------ 6 files changed, 87 insertions(+), 39 deletions(-) diff --git a/cln/cln_client.go b/cln/cln_client.go index 8475ce6..6125dde 100644 --- a/cln/cln_client.go +++ b/cln/cln_client.go @@ -65,7 +65,7 @@ func (c *ClnClient) IsConnected(destination []byte) (bool, error) { } for _, peer := range peers { - if pubKey == peer.Id { + if pubKey == peer.Id && peer.Connected { log.Printf("destination online: %x", destination) return true, nil } @@ -228,3 +228,29 @@ func (c *ClnClient) GetClosedChannels(nodeID string, channelPoints map[string]ui return r, nil } + +func (c *ClnClient) GetPeerId(scid *basetypes.ShortChannelID) ([]byte, error) { + scidStr := scid.ToString() + peers, err := c.client.ListPeers() + if err != nil { + return nil, err + } + + var dest *string + for _, p := range peers { + for _, ch := range p.Channels { + if ch.Alias.Local == scidStr || + ch.Alias.Remote == scidStr || + ch.ShortChannelId == scidStr { + dest = &p.Id + break + } + } + } + + if dest == nil { + return nil, nil + } + + return hex.DecodeString(*dest) +} diff --git a/cln/cln_interceptor.go b/cln/cln_interceptor.go index a2c6162..6f3e4b1 100644 --- a/cln/cln_interceptor.go +++ b/cln/cln_interceptor.go @@ -10,6 +10,7 @@ import ( "sync" "time" + "github.com/breez/lspd/basetypes" "github.com/breez/lspd/cln_plugin/proto" "github.com/breez/lspd/config" "github.com/breez/lspd/interceptor" @@ -129,20 +130,6 @@ func (i *ClnHtlcInterceptor) intercept() error { log.Printf("unexpected error in interceptor.Recv() %v", err) break } - nextHop := "" - channels, err := i.client.client.GetChannel(request.Onion.ShortChannelId) - if err != nil { - for _, c := range channels { - if c.Source == i.config.NodePubkey { - nextHop = c.Destination - break - } - if c.Destination == i.config.NodePubkey { - nextHop = c.Source - break - } - } - } i.doneWg.Add(1) go func() { @@ -150,8 +137,17 @@ func (i *ClnHtlcInterceptor) intercept() error { if err != nil { interceptorClient.Send(i.defaultResolution(request)) i.doneWg.Done() + return } - interceptResult := i.interceptor.Intercept(nextHop, paymentHash, request.Onion.ForwardMsat, request.Onion.OutgoingCltvValue, request.Htlc.CltvExpiry) + + scid, err := basetypes.NewShortChannelIDFromString(request.Onion.ShortChannelId) + if err != nil { + interceptorClient.Send(i.defaultResolution(request)) + i.doneWg.Done() + return + } + + interceptResult := i.interceptor.Intercept(scid, paymentHash, request.Onion.ForwardMsat, request.Onion.OutgoingCltvValue, request.Htlc.CltvExpiry) switch interceptResult.Action { case interceptor.INTERCEPT_RESUME_WITH_ONION: interceptorClient.Send(i.resumeWithOnion(request, interceptResult)) diff --git a/interceptor/intercept.go b/interceptor/intercept.go index ae7545d..35b6324 100644 --- a/interceptor/intercept.go +++ b/interceptor/intercept.go @@ -73,7 +73,7 @@ func NewInterceptor( } } -func (i *Interceptor) Intercept(nextHop string, reqPaymentHash []byte, reqOutgoingAmountMsat uint64, reqOutgoingExpiry uint32, reqIncomingExpiry uint32) InterceptResult { +func (i *Interceptor) Intercept(scid *basetypes.ShortChannelID, reqPaymentHash []byte, reqOutgoingAmountMsat uint64, reqOutgoingExpiry uint32, reqIncomingExpiry uint32) InterceptResult { reqPaymentHashStr := hex.EncodeToString(reqPaymentHash) resp, _, _ := i.payHashGroup.Do(reqPaymentHashStr, func() (interface{}, error) { token, params, paymentHash, paymentSecret, destination, incomingAmountMsat, outgoingAmountMsat, channelPoint, tag, err := i.store.PaymentInfo(reqPaymentHash) @@ -85,7 +85,24 @@ func (i *Interceptor) Intercept(nextHop string, reqPaymentHash []byte, reqOutgoi }, nil } - if paymentSecret == nil || (nextHop != "" && nextHop != hex.EncodeToString(destination)) { + nextHop, err := i.client.GetPeerId(scid) + if err != nil { + log.Printf("GetPeerId(%s) error: %v", scid.ToString(), err) + return InterceptResult{ + Action: INTERCEPT_FAIL_HTLC_WITH_CODE, + FailureCode: FAILURE_TEMPORARY_NODE_FAILURE, + }, nil + } + + // If the payment was registered, but the next hop is not the destination + // that means we are not the last hop of the payment, so we'll just forward. + if destination != nil && nextHop != nil && !bytes.Equal(nextHop, destination) { + return InterceptResult{ + Action: INTERCEPT_RESUME, + }, nil + } + + if paymentSecret == nil { return InterceptResult{ Action: INTERCEPT_RESUME, }, nil diff --git a/lightning/client.go b/lightning/client.go index d414deb..4109e41 100644 --- a/lightning/client.go +++ b/lightning/client.go @@ -31,6 +31,7 @@ type Client interface { IsConnected(destination []byte) (bool, error) OpenChannel(req *OpenChannelRequest) (*wire.OutPoint, error) GetChannel(peerID []byte, channelPoint wire.OutPoint) (*GetChannelResult, error) + GetPeerId(scid *basetypes.ShortChannelID) ([]byte, error) GetNodeChannelCount(nodeID []byte) (int, error) GetClosedChannels(nodeID string, channelPoints map[string]uint64) (map[string]uint64, error) } diff --git a/lnd/client.go b/lnd/client.go index 0105569..1194b37 100644 --- a/lnd/client.go +++ b/lnd/client.go @@ -79,18 +79,18 @@ func (c *LndClient) GetInfo() (*lightning.GetInfoResult, error) { } func (c *LndClient) IsConnected(destination []byte) (bool, error) { - pubKey := hex.EncodeToString(destination) + pubkey := hex.EncodeToString(destination) - r, err := c.client.ListPeers(context.Background(), &lnrpc.ListPeersRequest{LatestError: true}) + r, err := c.client.GetPeerConnected(context.Background(), &lnrpc.GetPeerConnectedRequest{ + Pubkey: pubkey, + }) if err != nil { - log.Printf("LND: client.ListPeers() error: %v", err) - return false, fmt.Errorf("LND: client.ListPeers() error: %w", err) + log.Printf("LND: client.GetPeerConnected() error: %v", err) + return false, fmt.Errorf("LND: client.GetPeerConnected() error: %w", err) } - for _, peer := range r.Peers { - if pubKey == peer.PubKey { - log.Printf("destination online: %x", destination) - return true, nil - } + if r.Connected { + log.Printf("LND: destination online: %x", destination) + return true, nil } log.Printf("LND: destination offline: %x", destination) @@ -230,3 +230,20 @@ func (c *LndClient) getWaitingCloseChannels(nodeID string) ([]*lnrpc.PendingChan } return waitingCloseChannels, nil } + +func (c *LndClient) GetPeerId(scid *basetypes.ShortChannelID) ([]byte, error) { + scidu64 := uint64(*scid) + peer, err := c.client.GetPeerIdByScid(context.Background(), &lnrpc.GetPeerIdByScidRequest{ + Scid: scidu64, + }) + if err != nil { + return nil, err + } + + if peer.PeerId == "" { + return nil, nil + } + + peerid, _ := hex.DecodeString(peer.PeerId) + return peerid, nil +} diff --git a/lnd/interceptor.go b/lnd/interceptor.go index 925a6eb..fa296d0 100644 --- a/lnd/interceptor.go +++ b/lnd/interceptor.go @@ -6,6 +6,7 @@ import ( "sync" "time" + "github.com/breez/lspd/basetypes" "github.com/breez/lspd/config" "github.com/breez/lspd/interceptor" "github.com/lightningnetwork/lnd/lnrpc" @@ -126,20 +127,10 @@ func (i *LndHtlcInterceptor) intercept() error { break } - nextHop := "" - chanInfo, err := i.client.client.GetChanInfo(context.Background(), &lnrpc.ChanInfoRequest{ChanId: request.OutgoingRequestedChanId}) - if err == nil && chanInfo != nil { - if chanInfo.Node1Pub == i.config.NodePubkey { - nextHop = chanInfo.Node2Pub - } - if chanInfo.Node2Pub == i.config.NodePubkey { - nextHop = chanInfo.Node1Pub - } - } - i.doneWg.Add(1) go func() { - interceptResult := i.interceptor.Intercept(nextHop, request.PaymentHash, request.OutgoingAmountMsat, request.OutgoingExpiry, request.IncomingExpiry) + scid := basetypes.ShortChannelID(request.OutgoingRequestedChanId) + interceptResult := i.interceptor.Intercept(&scid, request.PaymentHash, request.OutgoingAmountMsat, request.OutgoingExpiry, request.IncomingExpiry) switch interceptResult.Action { case interceptor.INTERCEPT_RESUME_WITH_ONION: interceptorClient.Send(&routerrpc.ForwardHtlcInterceptResponse{ From 4fc78877072c4098bdcf50fd257d65aad511f467 Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Thu, 15 Jun 2023 12:48:28 +0200 Subject: [PATCH 196/214] notifications: server implementation --- notifications/genproto.sh | 4 + notifications/notifications.pb.go | 218 +++++++++++++++++++++++++ notifications/notifications.proto | 18 ++ notifications/notifications_grpc.pb.go | 105 ++++++++++++ notifications/server.go | 58 +++++++ notifications/store.go | 10 ++ 6 files changed, 413 insertions(+) create mode 100755 notifications/genproto.sh create mode 100644 notifications/notifications.pb.go create mode 100644 notifications/notifications.proto create mode 100644 notifications/notifications_grpc.pb.go create mode 100644 notifications/server.go create mode 100644 notifications/store.go diff --git a/notifications/genproto.sh b/notifications/genproto.sh new file mode 100755 index 0000000..aff50da --- /dev/null +++ b/notifications/genproto.sh @@ -0,0 +1,4 @@ +#!/bin/bash +SCRIPTDIR=$(dirname $0) + +protoc --go_out=$SCRIPTDIR --go_opt=paths=source_relative --go-grpc_out=$SCRIPTDIR --go-grpc_opt=paths=source_relative -I=$SCRIPTDIR $SCRIPTDIR/*.proto diff --git a/notifications/notifications.pb.go b/notifications/notifications.pb.go new file mode 100644 index 0000000..36db039 --- /dev/null +++ b/notifications/notifications.pb.go @@ -0,0 +1,218 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.28.1 +// protoc v3.21.12 +// source: notifications.proto + +package notifications + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type SubscribeNotificationsRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Url string `protobuf:"bytes,1,opt,name=url,proto3" json:"url,omitempty"` + Signature []byte `protobuf:"bytes,2,opt,name=signature,proto3" json:"signature,omitempty"` +} + +func (x *SubscribeNotificationsRequest) Reset() { + *x = SubscribeNotificationsRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_notifications_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SubscribeNotificationsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SubscribeNotificationsRequest) ProtoMessage() {} + +func (x *SubscribeNotificationsRequest) ProtoReflect() protoreflect.Message { + mi := &file_notifications_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SubscribeNotificationsRequest.ProtoReflect.Descriptor instead. +func (*SubscribeNotificationsRequest) Descriptor() ([]byte, []int) { + return file_notifications_proto_rawDescGZIP(), []int{0} +} + +func (x *SubscribeNotificationsRequest) GetUrl() string { + if x != nil { + return x.Url + } + return "" +} + +func (x *SubscribeNotificationsRequest) GetSignature() []byte { + if x != nil { + return x.Signature + } + return nil +} + +type SubscribeNotificationsReply struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *SubscribeNotificationsReply) Reset() { + *x = SubscribeNotificationsReply{} + if protoimpl.UnsafeEnabled { + mi := &file_notifications_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SubscribeNotificationsReply) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SubscribeNotificationsReply) ProtoMessage() {} + +func (x *SubscribeNotificationsReply) ProtoReflect() protoreflect.Message { + mi := &file_notifications_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SubscribeNotificationsReply.ProtoReflect.Descriptor instead. +func (*SubscribeNotificationsReply) Descriptor() ([]byte, []int) { + return file_notifications_proto_rawDescGZIP(), []int{1} +} + +var File_notifications_proto protoreflect.FileDescriptor + +var file_notifications_proto_rawDesc = []byte{ + 0x0a, 0x13, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0d, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x4f, 0x0a, 0x1d, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, + 0x65, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, + 0x74, 0x75, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, + 0x61, 0x74, 0x75, 0x72, 0x65, 0x22, 0x1d, 0x0a, 0x1b, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, + 0x62, 0x65, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, + 0x65, 0x70, 0x6c, 0x79, 0x32, 0x85, 0x01, 0x0a, 0x0d, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x74, 0x0a, 0x16, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, + 0x69, 0x62, 0x65, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x12, 0x2c, 0x2e, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x2e, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, + 0x2e, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x53, + 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x42, 0x25, 0x5a, 0x23, + 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x62, 0x72, 0x65, 0x65, 0x7a, + 0x2f, 0x6c, 0x73, 0x70, 0x64, 0x2f, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_notifications_proto_rawDescOnce sync.Once + file_notifications_proto_rawDescData = file_notifications_proto_rawDesc +) + +func file_notifications_proto_rawDescGZIP() []byte { + file_notifications_proto_rawDescOnce.Do(func() { + file_notifications_proto_rawDescData = protoimpl.X.CompressGZIP(file_notifications_proto_rawDescData) + }) + return file_notifications_proto_rawDescData +} + +var file_notifications_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_notifications_proto_goTypes = []interface{}{ + (*SubscribeNotificationsRequest)(nil), // 0: notifications.SubscribeNotificationsRequest + (*SubscribeNotificationsReply)(nil), // 1: notifications.SubscribeNotificationsReply +} +var file_notifications_proto_depIdxs = []int32{ + 0, // 0: notifications.Notifications.SubscribeNotifications:input_type -> notifications.SubscribeNotificationsRequest + 1, // 1: notifications.Notifications.SubscribeNotifications:output_type -> notifications.SubscribeNotificationsReply + 1, // [1:2] is the sub-list for method output_type + 0, // [0:1] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_notifications_proto_init() } +func file_notifications_proto_init() { + if File_notifications_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_notifications_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SubscribeNotificationsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_notifications_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SubscribeNotificationsReply); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_notifications_proto_rawDesc, + NumEnums: 0, + NumMessages: 2, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_notifications_proto_goTypes, + DependencyIndexes: file_notifications_proto_depIdxs, + MessageInfos: file_notifications_proto_msgTypes, + }.Build() + File_notifications_proto = out.File + file_notifications_proto_rawDesc = nil + file_notifications_proto_goTypes = nil + file_notifications_proto_depIdxs = nil +} diff --git a/notifications/notifications.proto b/notifications/notifications.proto new file mode 100644 index 0000000..2590dff --- /dev/null +++ b/notifications/notifications.proto @@ -0,0 +1,18 @@ +syntax = "proto3"; + +option go_package = "github.com/breez/lspd/notifications"; + +package notifications; + +service Notifications { + rpc SubscribeNotifications(SubscribeNotificationsRequest) + returns (SubscribeNotificationsReply) {} +} + +message SubscribeNotificationsRequest { + string url = 1; + bytes signature = 2; +} + +message SubscribeNotificationsReply { +} \ No newline at end of file diff --git a/notifications/notifications_grpc.pb.go b/notifications/notifications_grpc.pb.go new file mode 100644 index 0000000..13a2f77 --- /dev/null +++ b/notifications/notifications_grpc.pb.go @@ -0,0 +1,105 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.2.0 +// - protoc v3.21.12 +// source: notifications.proto + +package notifications + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +// NotificationsClient is the client API for Notifications service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type NotificationsClient interface { + SubscribeNotifications(ctx context.Context, in *SubscribeNotificationsRequest, opts ...grpc.CallOption) (*SubscribeNotificationsReply, error) +} + +type notificationsClient struct { + cc grpc.ClientConnInterface +} + +func NewNotificationsClient(cc grpc.ClientConnInterface) NotificationsClient { + return ¬ificationsClient{cc} +} + +func (c *notificationsClient) SubscribeNotifications(ctx context.Context, in *SubscribeNotificationsRequest, opts ...grpc.CallOption) (*SubscribeNotificationsReply, error) { + out := new(SubscribeNotificationsReply) + err := c.cc.Invoke(ctx, "/notifications.Notifications/SubscribeNotifications", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// NotificationsServer is the server API for Notifications service. +// All implementations must embed UnimplementedNotificationsServer +// for forward compatibility +type NotificationsServer interface { + SubscribeNotifications(context.Context, *SubscribeNotificationsRequest) (*SubscribeNotificationsReply, error) + mustEmbedUnimplementedNotificationsServer() +} + +// UnimplementedNotificationsServer must be embedded to have forward compatible implementations. +type UnimplementedNotificationsServer struct { +} + +func (UnimplementedNotificationsServer) SubscribeNotifications(context.Context, *SubscribeNotificationsRequest) (*SubscribeNotificationsReply, error) { + return nil, status.Errorf(codes.Unimplemented, "method SubscribeNotifications not implemented") +} +func (UnimplementedNotificationsServer) mustEmbedUnimplementedNotificationsServer() {} + +// UnsafeNotificationsServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to NotificationsServer will +// result in compilation errors. +type UnsafeNotificationsServer interface { + mustEmbedUnimplementedNotificationsServer() +} + +func RegisterNotificationsServer(s grpc.ServiceRegistrar, srv NotificationsServer) { + s.RegisterService(&Notifications_ServiceDesc, srv) +} + +func _Notifications_SubscribeNotifications_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SubscribeNotificationsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(NotificationsServer).SubscribeNotifications(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/notifications.Notifications/SubscribeNotifications", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(NotificationsServer).SubscribeNotifications(ctx, req.(*SubscribeNotificationsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// Notifications_ServiceDesc is the grpc.ServiceDesc for Notifications service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var Notifications_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "notifications.Notifications", + HandlerType: (*NotificationsServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "SubscribeNotifications", + Handler: _Notifications_SubscribeNotifications_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "notifications.proto", +} diff --git a/notifications/server.go b/notifications/server.go new file mode 100644 index 0000000..ab73a18 --- /dev/null +++ b/notifications/server.go @@ -0,0 +1,58 @@ +package notifications + +import ( + context "context" + "crypto/sha256" + "encoding/hex" + "fmt" + "log" + + "github.com/btcsuite/btcd/btcec/v2/ecdsa" +) + +var ErrInvalidSignature = fmt.Errorf("invalid signature") +var ErrInternal = fmt.Errorf("internal error") + +type server struct { + store Store + NotificationsServer +} + +func NewNotificationsServer(store Store) NotificationsServer { + return &server{ + store: store, + } +} + +func (s *server) SubscribeNotifications( + ctx context.Context, + request *SubscribeNotificationsRequest, +) (*SubscribeNotificationsReply, error) { + first := sha256.Sum256([]byte(request.Url)) + second := sha256.Sum256(first[:]) + pubkey, wasCompressed, err := ecdsa.RecoverCompact( + request.Signature, + second[:], + ) + if err != nil { + return nil, ErrInvalidSignature + } + + if !wasCompressed { + return nil, ErrInvalidSignature + } + + err = s.store.Register(ctx, hex.EncodeToString(pubkey.SerializeCompressed()), request.Url) + if err != nil { + log.Printf( + "failed to register %x for notifications on url %s: %v", + pubkey.SerializeCompressed(), + request.Url, + err, + ) + + return nil, ErrInternal + } + + return &SubscribeNotificationsReply{}, nil +} diff --git a/notifications/store.go b/notifications/store.go new file mode 100644 index 0000000..fb9a352 --- /dev/null +++ b/notifications/store.go @@ -0,0 +1,10 @@ +package notifications + +import ( + "context" +) + +type Store interface { + Register(ctx context.Context, pubkey string, url string) error + GetRegistrations(ctx context.Context, pubkey string) ([]string, error) +} From 5aa1c4ea286346566aaf47f60a644e314fe3edeb Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Thu, 15 Jun 2023 12:48:46 +0200 Subject: [PATCH 197/214] notifications: postgres datastore implementation --- .../000013_notification_subscription.down.sql | 3 + .../000013_notification_subscription.up.sql | 10 +++ postgresql/notifications_store.go | 76 +++++++++++++++++++ 3 files changed, 89 insertions(+) create mode 100644 postgresql/migrations/000013_notification_subscription.down.sql create mode 100644 postgresql/migrations/000013_notification_subscription.up.sql create mode 100644 postgresql/notifications_store.go diff --git a/postgresql/migrations/000013_notification_subscription.down.sql b/postgresql/migrations/000013_notification_subscription.down.sql new file mode 100644 index 0000000..994d8c3 --- /dev/null +++ b/postgresql/migrations/000013_notification_subscription.down.sql @@ -0,0 +1,3 @@ +DROP INDEX notification_subscriptions_pubkey_url_key; +DROP INDEX notification_subscriptions_pubkey_idx; +DROP TABLE public.notification_subscriptions; diff --git a/postgresql/migrations/000013_notification_subscription.up.sql b/postgresql/migrations/000013_notification_subscription.up.sql new file mode 100644 index 0000000..d19cf2a --- /dev/null +++ b/postgresql/migrations/000013_notification_subscription.up.sql @@ -0,0 +1,10 @@ +CREATE TABLE public.notification_subscriptions ( + id bigserial primary key, + pubkey bytea NOT NULL, + url varchar NOT NULL, + created_at bigint NOT NULL, + refreshed_at bigint NOT NULL +); + +CREATE INDEX notification_subscriptions_pubkey_idx ON public.notification_subscriptions (pubkey); +CREATE UNIQUE INDEX notification_subscriptions_pubkey_url_key ON public.notification_subscriptions (pubkey, url); diff --git a/postgresql/notifications_store.go b/postgresql/notifications_store.go new file mode 100644 index 0000000..b0675a8 --- /dev/null +++ b/postgresql/notifications_store.go @@ -0,0 +1,76 @@ +package postgresql + +import ( + "context" + "encoding/hex" + "time" + + "github.com/jackc/pgx/v4/pgxpool" +) + +type NotificationsStore struct { + pool *pgxpool.Pool +} + +func NewNotificationsStore(pool *pgxpool.Pool) *NotificationsStore { + return &NotificationsStore{pool: pool} +} + +func (s *NotificationsStore) Register( + ctx context.Context, + pubkey string, + url string, +) error { + pk, err := hex.DecodeString(pubkey) + if err != nil { + return err + } + + now := time.Now().UnixMicro() + _, err = s.pool.Exec( + ctx, + `INSERT INTO public.notification_subscriptions (pubkey, url, created_at, refreshed_at) + values ($1, $2, $3, $4) + ON CONFLICT (pubkey, url) DO UPDATE SET refreshed_at = $4`, + pk, + url, + now, + now, + ) + + return err +} + +func (s *NotificationsStore) GetRegistrations( + ctx context.Context, + pubkey string, +) ([]string, error) { + pk, err := hex.DecodeString(pubkey) + if err != nil { + return nil, err + } + + rows, err := s.pool.Query( + ctx, + `SELECT url + FROM public.notification_subscriptions + WHERE pubkey = $1`, + pk, + ) + if err != nil { + return nil, err + } + + var result []string + for rows.Next() { + var url string + err = rows.Scan(&url) + if err != nil { + return nil, err + } + + result = append(result, url) + } + + return result, nil +} From d128848456495d2c7160eb75367ab302e2c23b41 Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Thu, 15 Jun 2023 12:55:58 +0200 Subject: [PATCH 198/214] notifications: host notifications server --- grpc_server.go | 5 +++++ main.go | 5 ++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/grpc_server.go b/grpc_server.go index 4c45bc9..d3055a5 100644 --- a/grpc_server.go +++ b/grpc_server.go @@ -13,6 +13,7 @@ import ( "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/caddyserver/certmagic" @@ -32,6 +33,7 @@ type grpcServer struct { s *grpc.Server nodes map[string]*node c lspdrpc.ChannelOpenerServer + n notifications.NotificationsServer } type nodeContext struct { @@ -54,6 +56,7 @@ func NewGrpcServer( address string, certmagicDomain string, c lspdrpc.ChannelOpenerServer, + n notifications.NotificationsServer, ) (*grpcServer, error) { if len(configs) == 0 { return nil, fmt.Errorf("no nodes supplied") @@ -115,6 +118,7 @@ func NewGrpcServer( certmagicDomain: certmagicDomain, nodes: nodes, c: c, + n: n, }, nil } @@ -178,6 +182,7 @@ func (s *grpcServer) Start() error { }), ) lspdrpc.RegisterChannelOpenerServer(srv, s.c) + notifications.RegisterNotificationsServer(srv, s.n) s.s = srv s.lis = lis diff --git a/main.go b/main.go index 9c20127..b6075c1 100644 --- a/main.go +++ b/main.go @@ -16,6 +16,7 @@ import ( "github.com/breez/lspd/interceptor" "github.com/breez/lspd/lnd" "github.com/breez/lspd/mempool" + "github.com/breez/lspd/notifications" "github.com/breez/lspd/postgresql" "github.com/btcsuite/btcd/btcec/v2" ) @@ -77,6 +78,7 @@ func main() { interceptStore := postgresql.NewPostgresInterceptStore(pool) forwardingStore := postgresql.NewForwardingEventStore(pool) + notificationsStore := postgresql.NewNotificationsStore(pool) var interceptors []interceptor.HtlcInterceptor for _, node := range nodes { @@ -118,7 +120,8 @@ func main() { address := os.Getenv("LISTEN_ADDRESS") certMagicDomain := os.Getenv("CERTMAGIC_DOMAIN") cs := NewChannelOpenerServer(interceptStore) - s, err := NewGrpcServer(nodes, address, certMagicDomain, cs) + ns := notifications.NewNotificationsServer(notificationsStore) + s, err := NewGrpcServer(nodes, address, certMagicDomain, cs, ns) if err != nil { log.Fatalf("failed to initialize grpc server: %v", err) } From 15c0df2e293ec1078d174ab2a2c9e103793d4948 Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Thu, 15 Jun 2023 12:56:57 +0200 Subject: [PATCH 199/214] notifications: implement notification service --- notifications/notification_service.go | 73 +++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 notifications/notification_service.go diff --git a/notifications/notification_service.go b/notifications/notification_service.go new file mode 100644 index 0000000..9797374 --- /dev/null +++ b/notifications/notification_service.go @@ -0,0 +1,73 @@ +package notifications + +import ( + "bytes" + "context" + "encoding/json" + "log" + "net/http" +) + +type NotificationService struct { + store Store +} + +func NewNotificationService(store Store) *NotificationService { + return &NotificationService{ + store: store, + } +} + +type PaymentReceivedPayload struct { + Template string `json:"template" binding:"required,eq=payment_received"` + Data struct { + PaymentHash string `json:"payment_hash" binding:"required"` + } `json:"data"` +} + +func (s *NotificationService) Notify( + pubkey string, + paymenthash string, +) (bool, error) { + registrations, err := s.store.GetRegistrations(context.Background(), pubkey) + if err != nil { + log.Printf("Failed to get notification registrations for %s: %v", pubkey, err) + return false, err + } + + req := &PaymentReceivedPayload{ + Template: "payment_received", + Data: struct { + PaymentHash string "json:\"payment_hash\" binding:\"required\"" + }{ + PaymentHash: paymenthash, + }, + } + + var buf bytes.Buffer + err = json.NewEncoder(&buf).Encode(req) + if err != nil { + log.Printf("Failed to encode payment notification for %s: %v", pubkey, err) + return false, err + } + + notified := false + for _, r := range registrations { + resp, err := http.DefaultClient.Post(r, "application/json", &buf) + if err != nil { + log.Printf("Failed to send payment notification for %s to %s: %v", pubkey, r, err) + // TODO: Remove subscription? + continue + } + + if resp.StatusCode != 200 { + log.Printf("Got non 200 status code (%s) for payment notification for %s to %s: %v", resp.Status, pubkey, r, err) + // TODO: Remove subscription? + continue + } + + notified = true + } + + return notified, nil +} From ddc0195b417059ad3c7d72109722f844771eee3b Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Thu, 15 Jun 2023 13:22:17 +0200 Subject: [PATCH 200/214] notifications: add WaitOnline + WaitChannelActive --- cln/cln_client.go | 23 ++++ lightning/client.go | 4 + lnd/client.go | 279 ++++++++++++++++++++++++++++++++++++++++++++ main.go | 1 + 4 files changed, 307 insertions(+) diff --git a/cln/cln_client.go b/cln/cln_client.go index 6125dde..c81674d 100644 --- a/cln/cln_client.go +++ b/cln/cln_client.go @@ -5,6 +5,7 @@ import ( "fmt" "log" "path/filepath" + "time" "github.com/breez/lspd/basetypes" "github.com/breez/lspd/lightning" @@ -254,3 +255,25 @@ func (c *ClnClient) GetPeerId(scid *basetypes.ShortChannelID) ([]byte, error) { return hex.DecodeString(*dest) } + +var pollingInterval = 400 * time.Millisecond + +func (c *ClnClient) WaitOnline(peerID []byte, deadline time.Time) error { + peerIDStr := hex.EncodeToString(peerID) + for { + peer, err := c.client.GetPeer(peerIDStr) + if err == nil && peer.Connected { + return nil + } + + select { + case <-time.After(time.Until(deadline)): + return fmt.Errorf("timeout") + case <-time.After(pollingInterval): + } + } +} + +func (c *ClnClient) WaitChannelActive(peerID []byte, deadline time.Time) error { + return nil +} diff --git a/lightning/client.go b/lightning/client.go index 4109e41..009e562 100644 --- a/lightning/client.go +++ b/lightning/client.go @@ -1,6 +1,8 @@ package lightning import ( + "time" + "github.com/breez/lspd/basetypes" "github.com/btcsuite/btcd/wire" ) @@ -34,4 +36,6 @@ type Client interface { GetPeerId(scid *basetypes.ShortChannelID) ([]byte, error) GetNodeChannelCount(nodeID []byte) (int, error) GetClosedChannels(nodeID string, channelPoints map[string]uint64) (map[string]uint64, error) + WaitOnline(peerID []byte, deadline time.Time) error + WaitChannelActive(peerID []byte, deadline time.Time) error } diff --git a/lnd/client.go b/lnd/client.go index 1194b37..90ae714 100644 --- a/lnd/client.go +++ b/lnd/client.go @@ -6,17 +6,22 @@ import ( "encoding/hex" "fmt" "log" + "sync" + "time" "github.com/breez/lspd/basetypes" "github.com/breez/lspd/config" "github.com/breez/lspd/lightning" + "github.com/btcsuite/btcd/chaincfg/chainhash" "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/codes" "google.golang.org/grpc/credentials" + "google.golang.org/grpc/status" ) type LndClient struct { @@ -24,6 +29,12 @@ type LndClient struct { routerClient routerrpc.RouterClient chainNotifierClient chainrpc.ChainNotifierClient conn *grpc.ClientConn + listenerCtx context.Context + listenerCancel context.CancelFunc + peersubs map[string]map[uint64]chan struct{} + chansubs map[string]map[uint64]chan struct{} + submtx sync.RWMutex + index uint64 } func NewLndClient(conf *config.LndConfig) (*LndClient, error) { @@ -58,13 +69,150 @@ func NewLndClient(conf *config.LndConfig) (*LndClient, error) { routerClient: routerClient, chainNotifierClient: chainNotifierClient, conn: conn, + peersubs: make(map[string]map[uint64]chan struct{}), + chansubs: make(map[string]map[uint64]chan struct{}), }, nil } func (c *LndClient) Close() { + cancel := c.listenerCancel + if cancel != nil { + cancel() + } c.conn.Close() } +func (c *LndClient) StartListeners() { + c.listenerCtx, c.listenerCancel = context.WithCancel(context.Background()) + go c.listenPeerEvents() + go c.listenChannelEvents() +} + +func (c *LndClient) listenPeerEvents() { + ctx := c.listenerCtx + for { + if ctx.Err() != nil { + return + } + + sub, err := c.client.SubscribePeerEvents( + ctx, + &lnrpc.PeerEventSubscription{}, + ) + if err != nil { + log.Printf("SubscribePeerEvents: %v", err) + <-time.After(time.Second) + continue + } + + for { + if ctx.Err() != nil { + return + } + + msg, err := sub.Recv() + if err != nil { + status, ok := status.FromError(err) + if ok && status.Code() == codes.Canceled { + log.Printf("listenPeerEvents: Got code canceled. Break.") + break + } + + log.Printf("unexpected error in listenPeerEvents: %v", err) + break + } + + if msg.Type != lnrpc.PeerEvent_PEER_ONLINE { + continue + } + + c.submtx.RLock() + subs, ok := c.peersubs[msg.PubKey] + if ok { + for _, sub := range subs { + sub <- struct{}{} + } + } + c.submtx.RUnlock() + } + + <-time.After(time.Second) + } +} + +func (c *LndClient) listenChannelEvents() { + ctx := c.listenerCtx + for { + if ctx.Err() != nil { + return + } + + sub, err := c.client.SubscribeChannelEvents( + ctx, + &lnrpc.ChannelEventSubscription{}, + ) + if err != nil { + log.Printf("listenChannelEvents: SubscribeChannelEvents: %v", err) + <-time.After(time.Second) + continue + } + + for { + if ctx.Err() != nil { + return + } + + msg, err := sub.Recv() + if err != nil { + status, ok := status.FromError(err) + if ok && status.Code() == codes.Canceled { + log.Printf("listenChannelEvents: Got code canceled. Break.") + break + } + + log.Printf("unexpected error in listenChannelEvents: %v", err) + break + } + + if msg.Type != lnrpc.ChannelEventUpdate_ACTIVE_CHANNEL { + continue + } + + ch := msg.GetActiveChannel() + point, err := extractChannelPoint(ch) + if err != nil { + log.Printf("listenChannelEvents: Failed to extract channel point %+v: %v", ch, err) + continue + } + + c.submtx.RLock() + subs, ok := c.chansubs[point] + if ok { + for _, sub := range subs { + sub <- struct{}{} + } + } + c.submtx.RUnlock() + } + + <-time.After(time.Second) + } +} + +func extractChannelPoint(cp *lnrpc.ChannelPoint) (string, error) { + str := cp.GetFundingTxidStr() + if str == "" { + b := cp.GetFundingTxidBytes() + h, err := chainhash.NewHash(b) + if err != nil { + return "", err + } + str = h.String() + } + + return fmt.Sprintf("%s:%d", str, cp.OutputIndex), nil +} + func (c *LndClient) GetInfo() (*lightning.GetInfoResult, error) { info, err := c.client.GetInfo(context.Background(), &lnrpc.GetInfoRequest{}) if err != nil { @@ -247,3 +395,134 @@ func (c *LndClient) GetPeerId(scid *basetypes.ShortChannelID) ([]byte, error) { peerid, _ := hex.DecodeString(peer.PeerId) return peerid, nil } + +func (c *LndClient) WaitOnline(peerID []byte, deadline time.Time) error { + pkStr := hex.EncodeToString(peerID) + signal := make(chan struct{}, 10) + defer close(signal) + + c.submtx.Lock() + subid := c.index + c.index++ + subs, ok := c.peersubs[pkStr] + if !ok { + subs = make(map[uint64]chan struct{}) + c.peersubs[pkStr] = subs + } + + subs[subid] = signal + c.submtx.Unlock() + + defer func() { + c.submtx.Lock() + subs, ok := c.peersubs[pkStr] + if ok { + delete(subs, subid) + if len(subs) == 0 { + delete(c.peersubs, pkStr) + } + } + c.submtx.Unlock() + }() + + connected, err := c.IsConnected(peerID) + if err != nil { + return err + } + if connected { + return nil + } + + select { + case <-signal: + return nil + case <-time.After(time.Until(deadline)): + return fmt.Errorf("deadline exceeded") + } +} + +func (c *LndClient) WaitChannelActive(peerID []byte, deadline time.Time) error { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + // Fetch the channels for this peer + chans, err := c.client.ListChannels(ctx, &lnrpc.ListChannelsRequest{ + Peer: peerID, + }) + if err != nil { + return err + } + if len(chans.Channels) == 0 { + return fmt.Errorf("no channels with peer") + } + + // Exit now if a channel is already active. + for _, ch := range chans.Channels { + if ch.Active { + return nil + } + } + + signal := make(chan struct{}, 10) + defer close(signal) + + // Subscribe to channel active events from this channel + c.submtx.Lock() + for _, ch := range chans.Channels { + chansignal := make(chan struct{}, 10) + defer close(chansignal) + + // forward signals from all channels to the signal aggregate + go func(c chan struct{}) { + for msg := range c { + signal <- msg + } + }(chansignal) + + outpoint := ch.ChannelPoint + subid := c.index + c.index++ + subs, ok := c.chansubs[outpoint] + if !ok { + subs = make(map[uint64]chan struct{}) + c.chansubs[outpoint] = subs + } + + subs[subid] = chansignal + defer func() { + c.submtx.Lock() + subs, ok := c.chansubs[outpoint] + if ok { + delete(subs, subid) + if len(subs) == 0 { + delete(c.chansubs, outpoint) + } + } + c.submtx.Unlock() + }() + } + c.submtx.Unlock() + + // Fetch the channels for this peer again, so there is no gap between the + // subscription and the call. + chans, err = c.client.ListChannels(ctx, &lnrpc.ListChannelsRequest{ + Peer: peerID, + }) + if err != nil { + return err + } + + // Exit now if a channel is already active. + for _, ch := range chans.Channels { + if ch.Active { + return nil + } + } + + select { + case <-signal: + return nil + case <-time.After(time.Until(deadline)): + return fmt.Errorf("deadline exceeded") + } +} diff --git a/main.go b/main.go index b6075c1..08d3446 100644 --- a/main.go +++ b/main.go @@ -89,6 +89,7 @@ func main() { log.Fatalf("failed to initialize LND client: %v", err) } + client.StartListeners() fwsync := lnd.NewForwardingHistorySync(client, interceptStore, forwardingStore) interceptor := interceptor.NewInterceptor(client, node, interceptStore, feeEstimator, feeStrategy) htlcInterceptor, err = lnd.NewLndHtlcInterceptor(node, client, fwsync, interceptor) From 09e8bd3cb6b5ebfe96babda2626f9f539f348014 Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Thu, 15 Jun 2023 15:55:09 +0200 Subject: [PATCH 201/214] notifications: notify htlc when peer offline --- config/config.go | 4 + interceptor/intercept.go | 230 +++++++++++++++++++++++++++----------- itest/bob_offline_test.go | 2 +- main.go | 5 +- 4 files changed, 173 insertions(+), 68 deletions(-) diff --git a/config/config.go b/config/config.go index 0f18c94..dd287e5 100644 --- a/config/config.go +++ b/config/config.go @@ -66,6 +66,10 @@ type NodeConfig struct { // The channel can be closed if not used this duration in seconds. MaxInactiveDuration uint64 `json:"maxInactiveDuration,string"` + // The maximum time to hold a htlc after sending a notification when the + // peer is offline. + NotificationTimeout string `json:"notificationTimeout,string"` + // Set this field to connect to an LND node. Lnd *LndConfig `json:"lnd,omitempty"` diff --git a/interceptor/intercept.go b/interceptor/intercept.go index 35b6324..cffa14c 100644 --- a/interceptor/intercept.go +++ b/interceptor/intercept.go @@ -13,6 +13,7 @@ import ( "github.com/breez/lspd/chain" "github.com/breez/lspd/config" "github.com/breez/lspd/lightning" + "github.com/breez/lspd/notifications" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/wire" sphinx "github.com/lightningnetwork/lightning-onion" @@ -49,12 +50,13 @@ type InterceptResult struct { } type Interceptor struct { - client lightning.Client - config *config.NodeConfig - store InterceptStore - feeEstimator chain.FeeEstimator - feeStrategy chain.FeeStrategy - payHashGroup singleflight.Group + client lightning.Client + config *config.NodeConfig + store InterceptStore + feeEstimator chain.FeeEstimator + feeStrategy chain.FeeStrategy + payHashGroup singleflight.Group + notificationService *notifications.NotificationService } func NewInterceptor( @@ -63,13 +65,15 @@ func NewInterceptor( store InterceptStore, feeEstimator chain.FeeEstimator, feeStrategy chain.FeeStrategy, + notificationService *notifications.NotificationService, ) *Interceptor { return &Interceptor{ - client: client, - config: config, - store: store, - feeEstimator: feeEstimator, - feeStrategy: feeStrategy, + client: client, + config: config, + store: store, + feeEstimator: feeEstimator, + feeStrategy: feeStrategy, + notificationService: notificationService, } } @@ -85,12 +89,13 @@ func (i *Interceptor) Intercept(scid *basetypes.ShortChannelID, reqPaymentHash [ }, nil } - nextHop, err := i.client.GetPeerId(scid) + isRegistered := paymentSecret != nil + isProbe := isRegistered && !bytes.Equal(paymentHash, reqPaymentHash) + nextHop, _ := i.client.GetPeerId(scid) if err != nil { log.Printf("GetPeerId(%s) error: %v", scid.ToString(), err) return InterceptResult{ - Action: INTERCEPT_FAIL_HTLC_WITH_CODE, - FailureCode: FAILURE_TEMPORARY_NODE_FAILURE, + Action: INTERCEPT_RESUME, }, nil } @@ -102,68 +107,163 @@ func (i *Interceptor) Intercept(scid *basetypes.ShortChannelID, reqPaymentHash [ }, nil } - if paymentSecret == nil { + // nextHop is set if the sender's scid corresponds to a known channel + // destination is set if the payment was registered for a channel open. + // The 'actual' next hop will be either of those. Or nil if the next hop + // is unknown. + if nextHop == nil { + nextHop = destination + } + + if nextHop != nil { + isConnected, err := i.client.IsConnected(nextHop) + if err != nil { + log.Printf("IsConnected(%x) error: %v", nextHop, err) + return InterceptResult{ + Action: INTERCEPT_FAIL_HTLC_WITH_CODE, + FailureCode: FAILURE_TEMPORARY_CHANNEL_FAILURE, + }, nil + } + + if !isConnected { + // If not connected, send a notification to the registered + // notification service for this client if available. + notified, err := i.notificationService.Notify( + hex.EncodeToString(nextHop), + reqPaymentHashStr, + ) + + // If this errors or the client is not notified, the client + // is offline or unknown. We'll resume the HTLC (which will + // result in UNKOWN_NEXT_PEER) + if err != nil { + return InterceptResult{ + Action: INTERCEPT_RESUME, + }, nil + } + + if notified { + log.Printf("Notified %x of pending htlc", nextHop) + d, err := time.ParseDuration(i.config.NotificationTimeout) + if err != nil { + log.Printf("WARN: No NotificationTimeout set. Using default 1m") + d = time.Minute + } + timeout := time.Now().Add(d) + err = i.client.WaitOnline(nextHop, timeout) + + // If there's an error waiting, resume the htlc. It will + // probably fail with UNKNOWN_NEXT_PEER. + if err != nil { + log.Printf( + "waiting for peer %x to come online failed with %v", + nextHop, + err, + ) + return InterceptResult{ + Action: INTERCEPT_RESUME, + }, nil + } + + log.Printf("Peer %x is back online. Continue htlc.", nextHop) + // At this point we know a few things. + // - This is either a channel partner or a registered payment + // - they were offline + // - They got notified about the htlc + // - They came back online + // So if this payment was not registered, this is a channel + // partner and we have to wait for the channel to become active + // before we can forward. + if !isRegistered { + err = i.client.WaitChannelActive(nextHop, timeout) + if err != nil { + log.Printf( + "waiting for channnel with %x to become active failed with %v", + nextHop, + err, + ) + return InterceptResult{ + Action: INTERCEPT_RESUME, + }, nil + } + } + } else if isProbe { + return InterceptResult{ + Action: INTERCEPT_FAIL_HTLC_WITH_CODE, + FailureCode: FAILURE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS, + }, nil + } else { + // If we haven't notified, resume the htlc. It will probably + // fail with UNKNOWN_NEXT_PEER. + return InterceptResult{ + Action: INTERCEPT_RESUME, + }, nil + } + } + } + + if !isRegistered { return InterceptResult{ Action: INTERCEPT_RESUME, }, nil } if channelPoint == nil { - if bytes.Equal(paymentHash, reqPaymentHash) { - // TODO: When opening_fee_params is enforced, turn this check in a temporary channel failure. - if params == nil { - log.Printf("DEPRECATED: Intercepted htlc with deprecated fee mechanism. Using default fees. payment hash: %s", reqPaymentHashStr) - params = &OpeningFeeParams{ - MinMsat: uint64(i.config.ChannelMinimumFeeMsat), - Proportional: uint32(i.config.ChannelFeePermyriad * 100), - ValidUntil: time.Now().UTC().Add(time.Duration(time.Hour * 24)).Format(basetypes.TIME_FORMAT), - MaxIdleTime: uint32(i.config.MaxInactiveDuration / 600), - MaxClientToSelfDelay: uint32(10000), - } - } - - if int64(reqIncomingExpiry)-int64(reqOutgoingExpiry) < int64(i.config.TimeLockDelta) { - return InterceptResult{ - Action: INTERCEPT_FAIL_HTLC_WITH_CODE, - FailureCode: FAILURE_TEMPORARY_CHANNEL_FAILURE, - }, nil - } - - validUntil, err := time.Parse(basetypes.TIME_FORMAT, params.ValidUntil) - if err != nil { - log.Printf("time.Parse(%s, %s) failed. Failing channel open: %v", basetypes.TIME_FORMAT, params.ValidUntil, err) - return InterceptResult{ - Action: INTERCEPT_FAIL_HTLC_WITH_CODE, - FailureCode: FAILURE_TEMPORARY_CHANNEL_FAILURE, - }, nil - } - - if time.Now().UTC().After(validUntil) { - if !i.isCurrentChainFeeCheaper(token, params) { - log.Printf("Intercepted expired payment registration. Failing payment. payment hash: %x, valid until: %s", paymentHash, params.ValidUntil) - return InterceptResult{ - Action: INTERCEPT_FAIL_HTLC_WITH_CODE, - FailureCode: FAILURE_TEMPORARY_CHANNEL_FAILURE, - }, nil - } - - log.Printf("Intercepted expired payment registration. Opening channel anyway, because it's cheaper at the current rate. paymenthash: %s, params: %+v", reqPaymentHashStr, params) - } - - channelPoint, err = i.openChannel(reqPaymentHash, destination, incomingAmountMsat, tag) - if err != nil { - log.Printf("openChannel(%x, %v) err: %v", destination, incomingAmountMsat, err) - return InterceptResult{ - Action: INTERCEPT_FAIL_HTLC_WITH_CODE, - FailureCode: FAILURE_TEMPORARY_CHANNEL_FAILURE, - }, nil - } - } else { //probing + if isProbe { return InterceptResult{ Action: INTERCEPT_FAIL_HTLC_WITH_CODE, FailureCode: FAILURE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS, }, nil } + + // TODO: When opening_fee_params is enforced, turn this check in a temporary channel failure. + if params == nil { + log.Printf("DEPRECATED: Intercepted htlc with deprecated fee mechanism. Using default fees. payment hash: %s", reqPaymentHashStr) + params = &OpeningFeeParams{ + MinMsat: uint64(i.config.ChannelMinimumFeeMsat), + Proportional: uint32(i.config.ChannelFeePermyriad * 100), + ValidUntil: time.Now().UTC().Add(time.Duration(time.Hour * 24)).Format(basetypes.TIME_FORMAT), + MaxIdleTime: uint32(i.config.MaxInactiveDuration / 600), + MaxClientToSelfDelay: uint32(10000), + } + } + + if int64(reqIncomingExpiry)-int64(reqOutgoingExpiry) < int64(i.config.TimeLockDelta) { + return InterceptResult{ + Action: INTERCEPT_FAIL_HTLC_WITH_CODE, + FailureCode: FAILURE_TEMPORARY_CHANNEL_FAILURE, + }, nil + } + + validUntil, err := time.Parse(basetypes.TIME_FORMAT, params.ValidUntil) + if err != nil { + log.Printf("time.Parse(%s, %s) failed. Failing channel open: %v", basetypes.TIME_FORMAT, params.ValidUntil, err) + return InterceptResult{ + Action: INTERCEPT_FAIL_HTLC_WITH_CODE, + FailureCode: FAILURE_TEMPORARY_CHANNEL_FAILURE, + }, nil + } + + if time.Now().UTC().After(validUntil) { + if !i.isCurrentChainFeeCheaper(token, params) { + log.Printf("Intercepted expired payment registration. Failing payment. payment hash: %x, valid until: %s", paymentHash, params.ValidUntil) + return InterceptResult{ + Action: INTERCEPT_FAIL_HTLC_WITH_CODE, + FailureCode: FAILURE_TEMPORARY_CHANNEL_FAILURE, + }, nil + } + + log.Printf("Intercepted expired payment registration. Opening channel anyway, because it's cheaper at the current rate. paymenthash: %s, params: %+v", reqPaymentHashStr, params) + } + + channelPoint, err = i.openChannel(reqPaymentHash, destination, incomingAmountMsat, tag) + if err != nil { + log.Printf("openChannel(%x, %v) err: %v", destination, incomingAmountMsat, err) + return InterceptResult{ + Action: INTERCEPT_FAIL_HTLC_WITH_CODE, + FailureCode: FAILURE_TEMPORARY_CHANNEL_FAILURE, + }, nil + } } pubKey, err := btcec.ParsePubKey(destination) diff --git a/itest/bob_offline_test.go b/itest/bob_offline_test.go index 8b214b7..9e04fda 100644 --- a/itest/bob_offline_test.go +++ b/itest/bob_offline_test.go @@ -57,7 +57,7 @@ func testFailureBobOffline(p *testParams) { log.Printf("Alice paying") route := constructRoute(p.lsp.LightningNode(), p.BreezClient().Node(), channelId, lntest.NewShortChanIDFromString("1x0x0"), outerAmountMsat) _, err := alice.PayViaRoute(outerAmountMsat, outerInvoice.paymentHash, outerInvoice.paymentSecret, route) - assert.Contains(p.t, err.Error(), "WIRE_TEMPORARY_CHANNEL_FAILURE") + assert.Contains(p.t, err.Error(), "WIRE_UNKNOWN_NEXT_PEER") log.Printf("Starting breez client again") p.BreezClient().Start() diff --git a/main.go b/main.go index 08d3446..2965738 100644 --- a/main.go +++ b/main.go @@ -79,6 +79,7 @@ func main() { interceptStore := postgresql.NewPostgresInterceptStore(pool) forwardingStore := postgresql.NewForwardingEventStore(pool) notificationsStore := postgresql.NewNotificationsStore(pool) + notificationService := notifications.NewNotificationService(notificationsStore) var interceptors []interceptor.HtlcInterceptor for _, node := range nodes { @@ -91,7 +92,7 @@ func main() { client.StartListeners() fwsync := lnd.NewForwardingHistorySync(client, interceptStore, forwardingStore) - interceptor := interceptor.NewInterceptor(client, node, interceptStore, feeEstimator, feeStrategy) + interceptor := interceptor.NewInterceptor(client, node, interceptStore, feeEstimator, feeStrategy, notificationService) htlcInterceptor, err = lnd.NewLndHtlcInterceptor(node, client, fwsync, interceptor) if err != nil { log.Fatalf("failed to initialize LND interceptor: %v", err) @@ -104,7 +105,7 @@ func main() { log.Fatalf("failed to initialize CLN client: %v", err) } - interceptor := interceptor.NewInterceptor(client, node, interceptStore, feeEstimator, feeStrategy) + interceptor := interceptor.NewInterceptor(client, node, interceptStore, feeEstimator, feeStrategy, notificationService) htlcInterceptor, err = cln.NewClnHtlcInterceptor(node, client, interceptor) if err != nil { log.Fatalf("failed to initialize CLN interceptor: %v", err) From 1558636890cfd77242338b2991064ce5f60ae381 Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Fri, 16 Jun 2023 11:00:13 +0200 Subject: [PATCH 202/214] notifications: add integration tests --- go.mod | 2 +- itest/breez_client.go | 74 +++++--- itest/cln_breez_client.go | 14 +- itest/cln_lspd_node.go | 24 ++- itest/lnd_breez_client.go | 4 + itest/lnd_lspd_node.go | 24 ++- itest/lspd_node.go | 2 + itest/lspd_test.go | 12 ++ itest/notification_service.go | 59 +++++++ itest/notification_test.go | 309 ++++++++++++++++++++++++++++++++++ 10 files changed, 474 insertions(+), 50 deletions(-) create mode 100644 itest/notification_service.go create mode 100644 itest/notification_test.go diff --git a/go.mod b/go.mod index daa6930..d46b271 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.19 require ( github.com/aws/aws-sdk-go v1.34.0 - github.com/breez/lntest v0.0.21 + github.com/breez/lntest v0.0.23 github.com/btcsuite/btcd v0.23.5-0.20230228185050-38331963bddd github.com/btcsuite/btcd/btcec/v2 v2.3.2 github.com/btcsuite/btcd/chaincfg/chainhash v1.0.2 diff --git a/itest/breez_client.go b/itest/breez_client.go index 7b96ca2..80f1fa8 100644 --- a/itest/breez_client.go +++ b/itest/breez_client.go @@ -3,6 +3,7 @@ package itest import ( "crypto/sha256" "log" + "testing" "github.com/breez/lntest" "github.com/btcsuite/btcd/btcec/v2" @@ -19,6 +20,7 @@ type BreezClient interface { Start() Stop() error SetHtlcAcceptor(totalMsat uint64) + ResetHtlcAcceptor() } type generateInvoicesRequest struct { @@ -39,24 +41,55 @@ func GenerateInvoices(n BreezClient, req generateInvoicesRequest) (invoice, invo preimage, err := GenerateRandomBytes(32) lntest.CheckError(n.Harness().T, err) - lspNodeId, err := btcec.ParsePubKey(req.lsp.NodeId()) - lntest.CheckError(n.Harness().T, err) - innerInvoice := n.Node().CreateBolt11Invoice(&lntest.CreateInvoiceOptions{ AmountMsat: req.innerAmountMsat, Description: &req.description, Preimage: &preimage, }) - outerInvoiceRaw, err := zpay32.Decode(innerInvoice.Bolt11, &chaincfg.RegressionNetParams) + outerInvoice := AddHopHint(n, innerInvoice.Bolt11, req.lsp, lntest.ShortChannelID{ + BlockHeight: 1, + TxIndex: 0, + OutputIndex: 0, + }, &req.outerAmountMsat) + + inner := invoice{ + bolt11: innerInvoice.Bolt11, + paymentHash: innerInvoice.PaymentHash, + paymentSecret: innerInvoice.PaymentSecret, + paymentPreimage: preimage, + } + outer := invoice{ + bolt11: outerInvoice, + paymentHash: innerInvoice.PaymentHash[:], + paymentSecret: innerInvoice.PaymentSecret, + paymentPreimage: preimage, + } + + return inner, outer +} + +func ContainsHopHint(t *testing.T, invoice string) bool { + rawInvoice, err := zpay32.Decode(invoice, &chaincfg.RegressionNetParams) + lntest.CheckError(t, err) + + return len(rawInvoice.RouteHints) > 0 +} + +func AddHopHint(n BreezClient, invoice string, lsp LspNode, chanid lntest.ShortChannelID, amountMsat *uint64) string { + rawInvoice, err := zpay32.Decode(invoice, &chaincfg.RegressionNetParams) lntest.CheckError(n.Harness().T, err) - milliSat := lnwire.MilliSatoshi(req.outerAmountMsat) - outerInvoiceRaw.MilliSat = &milliSat - fakeChanId := &lnwire.ShortChannelID{BlockHeight: 1, TxIndex: 0, TxPosition: 0} - outerInvoiceRaw.RouteHints = append(outerInvoiceRaw.RouteHints, []zpay32.HopHint{ + if amountMsat != nil { + milliSat := lnwire.MilliSatoshi(*amountMsat) + rawInvoice.MilliSat = &milliSat + } + + lspNodeId, err := btcec.ParsePubKey(lsp.NodeId()) + lntest.CheckError(n.Harness().T, err) + rawInvoice.RouteHints = append(rawInvoice.RouteHints, []zpay32.HopHint{ { NodeID: lspNodeId, - ChannelID: fakeChanId.ToUint64(), + ChannelID: chanid.ToUint64(), FeeBaseMSat: lspBaseFeeMsat, FeeProportionalMillionths: lspFeeRatePpm, CLTVExpiryDelta: lspCltvDelta, @@ -64,12 +97,12 @@ func GenerateInvoices(n BreezClient, req generateInvoicesRequest) (invoice, invo }) log.Printf( - "Encoding outer invoice. privkey: '%x', invoice: '%+v', original bolt11: '%s'", + "Encoding invoice. privkey: '%x', invoice: '%+v', original bolt11: '%s'", n.Node().PrivateKey().Serialize(), - outerInvoiceRaw, - innerInvoice.Bolt11, + rawInvoice, + invoice, ) - outerInvoice, err := outerInvoiceRaw.Encode(zpay32.MessageSigner{ + newInvoice, err := rawInvoice.Encode(zpay32.MessageSigner{ SignCompact: func(msg []byte) ([]byte, error) { hash := sha256.Sum256(msg) sig, err := ecdsa.SignCompact(n.Node().PrivateKey(), hash[:], true) @@ -85,18 +118,5 @@ func GenerateInvoices(n BreezClient, req generateInvoicesRequest) (invoice, invo }) lntest.CheckError(n.Harness().T, err) - inner := invoice{ - bolt11: innerInvoice.Bolt11, - paymentHash: innerInvoice.PaymentHash, - paymentSecret: innerInvoice.PaymentSecret, - paymentPreimage: preimage, - } - outer := invoice{ - bolt11: outerInvoice, - paymentHash: outerInvoiceRaw.PaymentHash[:], - paymentSecret: innerInvoice.PaymentSecret, - paymentPreimage: preimage, - } - - return inner, outer + return newInvoice } diff --git a/itest/cln_breez_client.go b/itest/cln_breez_client.go index 369ba3b..ee2ad66 100644 --- a/itest/cln_breez_client.go +++ b/itest/cln_breez_client.go @@ -123,6 +123,9 @@ func (c *clnBreezClient) Start() { c.startHtlcAcceptor() } +func (c *clnBreezClient) ResetHtlcAcceptor() { + c.htlcAcceptor = nil +} func (c *clnBreezClient) SetHtlcAcceptor(totalMsat uint64) { c.htlcAcceptor = func(htlc *proto.HtlcAccepted) *proto.HtlcResolution { origPayload, err := hex.DecodeString(htlc.Onion.Payload) @@ -230,13 +233,12 @@ func (c *clnBreezClient) startHtlcAcceptor() { } client := proto.NewClnPluginClient(conn) + acceptor, err := client.HtlcStream(ctx) + if err != nil { + log.Printf("%s: client.HtlcStream() error: %v", c.name, err) + break + } for { - acceptor, err := client.HtlcStream(ctx) - if err != nil { - log.Printf("%s: client.HtlcStream() error: %v", c.name, err) - break - } - htlc, err := acceptor.Recv() if err != nil { log.Printf("%s: acceptor.Recv() error: %v", c.name, err) diff --git a/itest/cln_lspd_node.go b/itest/cln_lspd_node.go index f5f94bc..fcfa646 100644 --- a/itest/cln_lspd_node.go +++ b/itest/cln_lspd_node.go @@ -12,6 +12,7 @@ import ( "github.com/breez/lntest" "github.com/breez/lspd/config" + "github.com/breez/lspd/notifications" lspd "github.com/breez/lspd/rpc" "github.com/btcsuite/btcd/btcec/v2" ecies "github.com/ecies/go/v2" @@ -36,10 +37,11 @@ type ClnLspNode struct { } type clnLspNodeRuntime struct { - logFile *os.File - cmd *exec.Cmd - rpc lspd.ChannelOpenerClient - cleanups []*lntest.Cleanup + logFile *os.File + cmd *exec.Cmd + rpc lspd.ChannelOpenerClient + notificationRpc notifications.NotificationsClient + cleanups []*lntest.Cleanup } func NewClnLspdNode(h *lntest.TestHarness, m *lntest.Miner, mem *mempoolApi, name string, nodeConfig *config.NodeConfig) LspNode { @@ -170,11 +172,13 @@ func (c *ClnLspNode) Start() { }) client := lspd.NewChannelOpenerClient(conn) + notif := notifications.NewNotificationsClient(conn) c.runtime = &clnLspNodeRuntime{ - logFile: logFile, - cmd: cmd, - rpc: client, - cleanups: cleanups, + logFile: logFile, + cmd: cmd, + rpc: client, + notificationRpc: notif, + cleanups: cleanups, } } @@ -207,6 +211,10 @@ func (c *ClnLspNode) Rpc() lspd.ChannelOpenerClient { return c.runtime.rpc } +func (c *ClnLspNode) NotificationsRpc() notifications.NotificationsClient { + return c.runtime.notificationRpc +} + func (l *ClnLspNode) NodeId() []byte { return l.lightningNode.NodeId() } diff --git a/itest/lnd_breez_client.go b/itest/lnd_breez_client.go index 66fe7fb..c95febd 100644 --- a/itest/lnd_breez_client.go +++ b/itest/lnd_breez_client.go @@ -78,6 +78,10 @@ func (c *lndBreezClient) Stop() error { return c.node.Stop() } +func (c *lndBreezClient) ResetHtlcAcceptor() { + +} + func (c *lndBreezClient) SetHtlcAcceptor(totalMsat uint64) { // No need for a htlc acceptor in the LND breez client } diff --git a/itest/lnd_lspd_node.go b/itest/lnd_lspd_node.go index 4864815..745b14b 100644 --- a/itest/lnd_lspd_node.go +++ b/itest/lnd_lspd_node.go @@ -14,6 +14,7 @@ import ( "github.com/breez/lntest" "github.com/breez/lspd/config" + "github.com/breez/lspd/notifications" lspd "github.com/breez/lspd/rpc" "github.com/btcsuite/btcd/btcec/v2" ecies "github.com/ecies/go/v2" @@ -32,10 +33,11 @@ type LndLspNode struct { } type lndLspNodeRuntime struct { - logFile *os.File - cmd *exec.Cmd - rpc lspd.ChannelOpenerClient - cleanups []*lntest.Cleanup + logFile *os.File + cmd *exec.Cmd + rpc lspd.ChannelOpenerClient + notificationRpc notifications.NotificationsClient + cleanups []*lntest.Cleanup } func NewLndLspdNode(h *lntest.TestHarness, m *lntest.Miner, mem *mempoolApi, name string, nodeConfig *config.NodeConfig) LspNode { @@ -193,11 +195,13 @@ func (c *LndLspNode) Start() { }) client := lspd.NewChannelOpenerClient(conn) + notifyClient := notifications.NewNotificationsClient(conn) c.runtime = &lndLspNodeRuntime{ - logFile: logFile, - cmd: cmd, - rpc: client, - cleanups: cleanups, + logFile: logFile, + cmd: cmd, + rpc: client, + notificationRpc: notifyClient, + cleanups: cleanups, } } @@ -230,6 +234,10 @@ func (c *LndLspNode) Rpc() lspd.ChannelOpenerClient { return c.runtime.rpc } +func (c *LndLspNode) NotificationsRpc() notifications.NotificationsClient { + return c.runtime.notificationRpc +} + func (l *LndLspNode) NodeId() []byte { return l.lightningNode.NodeId() } diff --git a/itest/lspd_node.go b/itest/lspd_node.go index cd926de..fcbb5ec 100644 --- a/itest/lspd_node.go +++ b/itest/lspd_node.go @@ -15,6 +15,7 @@ import ( "github.com/breez/lntest" "github.com/breez/lspd/config" + "github.com/breez/lspd/notifications" lspd "github.com/breez/lspd/rpc" "github.com/btcsuite/btcd/btcec/v2" "github.com/decred/dcrd/dcrec/secp256k1/v4" @@ -45,6 +46,7 @@ type LspNode interface { PublicKey() *btcec.PublicKey EciesPublicKey() *ecies.PublicKey Rpc() lspd.ChannelOpenerClient + NotificationsRpc() notifications.NotificationsClient NodeId() []byte LightningNode() lntest.LightningNode PostgresBackend() *PostgresContainer diff --git a/itest/lspd_test.go b/itest/lspd_test.go index 4f403de..098d68a 100644 --- a/itest/lspd_test.go +++ b/itest/lspd_test.go @@ -151,4 +151,16 @@ var allTestCases = []*testCase{ name: "testDynamicFeeFlow", test: testDynamicFeeFlow, }, + { + name: "testOfflineNotificationPaymentRegistered", + test: testOfflineNotificationPaymentRegistered, + }, + { + name: "testOfflineNotificationRegularForward", + test: testOfflineNotificationRegularForward, + }, + { + name: "testOfflineNotificationZeroConfChannel", + test: testOfflineNotificationZeroConfChannel, + }, } diff --git a/itest/notification_service.go b/itest/notification_service.go new file mode 100644 index 0000000..85c856a --- /dev/null +++ b/itest/notification_service.go @@ -0,0 +1,59 @@ +package itest + +import ( + "context" + "net" + "net/http" +) + +type PaymentReceivedPayload struct { + Template string `json:"template" binding:"required,eq=payment_received"` + Data struct { + PaymentHash string `json:"payment_hash" binding:"required"` + } `json:"data"` +} + +type TxConfirmedPayload struct { + Template string `json:"template" binding:"required,eq=tx_confirmed"` + Data struct { + TxID string `json:"tx_id" binding:"required"` + } `json:"data"` +} + +type AddressTxsChangedPayload struct { + Template string `json:"template" binding:"required,eq=address_txs_changed"` + Data struct { + Address string `json:"address" binding:"required"` + } `json:"data"` +} + +type notificationDeliveryService struct { + addr string + handleFunc func(resp http.ResponseWriter, req *http.Request) +} + +func newNotificationDeliveryService( + addr string, + handleFunc func(resp http.ResponseWriter, req *http.Request), +) *notificationDeliveryService { + return ¬ificationDeliveryService{ + addr: addr, + handleFunc: handleFunc, + } +} + +func (s *notificationDeliveryService) Start(ctx context.Context) error { + mux := http.NewServeMux() + mux.HandleFunc("/api/v1/notify", s.handleFunc) + lis, err := net.Listen("tcp", s.addr) + if err != nil { + return err + } + + go func() { + <-ctx.Done() + lis.Close() + }() + + return http.Serve(lis, mux) +} diff --git a/itest/notification_test.go b/itest/notification_test.go new file mode 100644 index 0000000..d4c0daf --- /dev/null +++ b/itest/notification_test.go @@ -0,0 +1,309 @@ +package itest + +import ( + "crypto/sha256" + "encoding/hex" + "encoding/json" + "fmt" + "log" + "net/http" + "time" + + "github.com/breez/lntest" + "github.com/breez/lspd/notifications" + lspd "github.com/breez/lspd/rpc" + "github.com/btcsuite/btcd/btcec/v2/ecdsa" + "github.com/stretchr/testify/assert" +) + +func testOfflineNotificationPaymentRegistered(p *testParams) { + alice := lntest.NewClnNode(p.h, p.m, "Alice") + alice.Start() + alice.Fund(10000000) + p.lsp.LightningNode().Fund(10000000) + + log.Print("Opening channel between Alice and the lsp") + channel := alice.OpenChannel(p.lsp.LightningNode(), &lntest.OpenChannelOptions{ + AmountSat: publicChanAmount, + }) + channelId := alice.WaitForChannelReady(channel) + + log.Printf("Adding bob's invoices") + outerAmountMsat := uint64(2100000) + innerAmountMsat := calculateInnerAmountMsat(p.lsp, outerAmountMsat, nil) + description := "Please pay me" + innerInvoice, outerInvoice := GenerateInvoices(p.BreezClient(), + generateInvoicesRequest{ + innerAmountMsat: innerAmountMsat, + outerAmountMsat: outerAmountMsat, + description: description, + lsp: p.lsp, + }) + + log.Print("Connecting bob to lspd") + p.BreezClient().Node().ConnectPeer(p.lsp.LightningNode()) + + log.Printf("Registering payment with lsp") + RegisterPayment(p.lsp, &lspd.PaymentInformation{ + PaymentHash: innerInvoice.paymentHash, + PaymentSecret: innerInvoice.paymentSecret, + Destination: p.BreezClient().Node().NodeId(), + IncomingAmountMsat: int64(outerAmountMsat), + OutgoingAmountMsat: int64(innerAmountMsat), + }, false) + + // Kill the mobile client + log.Printf("Stopping breez client") + p.BreezClient().Stop() + + port, err := lntest.GetPort() + if err != nil { + assert.FailNow(p.t, "failed to get port for deliveeery service") + } + addr := fmt.Sprintf("127.0.0.1:%d", port) + delivered := make(chan struct{}) + + notify := newNotificationDeliveryService(addr, func(resp http.ResponseWriter, req *http.Request) { + var body PaymentReceivedPayload + err = json.NewDecoder(req.Body).Decode(&body) + assert.NoError(p.t, err) + + ph := hex.EncodeToString(innerInvoice.paymentHash) + assert.Equal(p.t, ph, body.Data.PaymentHash) + close(delivered) + }) + go notify.Start(p.h.Ctx) + go func() { + <-delivered + log.Printf("Starting breez client again") + p.BreezClient().SetHtlcAcceptor(innerAmountMsat) + p.BreezClient().Start() + p.BreezClient().Node().ConnectPeer(p.lsp.LightningNode()) + }() + + // TODO: Fix race waiting for htlc interceptor. + log.Printf("Waiting %v to allow htlc interceptor to activate.", htlcInterceptorDelay) + <-time.After(htlcInterceptorDelay) + + url := "http://" + addr + "/api/v1/notify" + first := sha256.Sum256([]byte(url)) + second := sha256.Sum256(first[:]) + sig, err := ecdsa.SignCompact(p.BreezClient().Node().PrivateKey(), second[:], true) + assert.NoError(p.t, err) + + p.lsp.NotificationsRpc().SubscribeNotifications(p.h.Ctx, ¬ifications.SubscribeNotificationsRequest{ + Url: url, + Signature: sig, + }) + log.Printf("Alice paying") + route := constructRoute(p.lsp.LightningNode(), p.BreezClient().Node(), channelId, lntest.NewShortChanIDFromString("1x0x0"), outerAmountMsat) + _, err = alice.PayViaRoute(outerAmountMsat, outerInvoice.paymentHash, outerInvoice.paymentSecret, route) + assert.Nil(p.t, err) +} + +func testOfflineNotificationRegularForward(p *testParams) { + alice := lntest.NewClnNode(p.h, p.m, "Alice") + alice.Start() + alice.Fund(10000000) + p.lsp.LightningNode().Fund(10000000) + p.BreezClient().Node().Fund(100000) + + log.Print("Opening channel between Alice and the lsp") + channelAL := alice.OpenChannel(p.lsp.LightningNode(), &lntest.OpenChannelOptions{ + AmountSat: publicChanAmount, + IsPublic: true, + }) + + log.Print("Opening channel between lsp and Breez client") + channelLB := p.lsp.LightningNode().OpenChannel(p.BreezClient().Node(), &lntest.OpenChannelOptions{ + AmountSat: 200000, + IsPublic: false, + }) + + log.Print("Waiting for channel between Alice and the lsp to be ready.") + alice.WaitForChannelReady(channelAL) + log.Print("Waiting for channel between LSP and Bob to be ready.") + p.lsp.LightningNode().WaitForChannelReady(channelLB) + p.BreezClient().Node().WaitForChannelReady(channelLB) + + port, err := lntest.GetPort() + if err != nil { + assert.FailNow(p.t, "failed to get port for deliveeery service") + } + addr := fmt.Sprintf("127.0.0.1:%d", port) + delivered := make(chan struct{}) + + notify := newNotificationDeliveryService(addr, func(resp http.ResponseWriter, req *http.Request) { + var body PaymentReceivedPayload + err = json.NewDecoder(req.Body).Decode(&body) + assert.NoError(p.t, err) + + close(delivered) + }) + go notify.Start(p.h.Ctx) + go func() { + <-delivered + log.Printf("Notification was delivered. Starting breez client again") + p.BreezClient().Start() + p.BreezClient().Node().ConnectPeer(p.lsp.LightningNode()) + }() + + url := "http://" + addr + "/api/v1/notify" + first := sha256.Sum256([]byte(url)) + second := sha256.Sum256(first[:]) + sig, err := ecdsa.SignCompact(p.BreezClient().Node().PrivateKey(), second[:], true) + assert.NoError(p.t, err) + + p.lsp.NotificationsRpc().SubscribeNotifications(p.h.Ctx, ¬ifications.SubscribeNotificationsRequest{ + Url: url, + Signature: sig, + }) + + <-time.After(time.Second * 2) + log.Printf("Adding bob's invoice") + amountMsat := uint64(2100000) + bobInvoice := p.BreezClient().Node().CreateBolt11Invoice(&lntest.CreateInvoiceOptions{ + AmountMsat: amountMsat, + IncludeHopHints: true, + }) + log.Printf(bobInvoice.Bolt11) + + log.Printf("Bob going offline") + p.BreezClient().Stop() + + // TODO: Fix race waiting for htlc interceptor. + log.Printf("Waiting %v to allow htlc interceptor to activate.", htlcInterceptorDelay) + <-time.After(htlcInterceptorDelay) + + log.Printf("Alice paying") + payResp := alice.Pay(bobInvoice.Bolt11) + invoiceResult := p.BreezClient().Node().GetInvoice(bobInvoice.PaymentHash) + + assert.Equal(p.t, payResp.PaymentPreimage, invoiceResult.PaymentPreimage) + assert.Equal(p.t, amountMsat, invoiceResult.AmountReceivedMsat) +} + +func testOfflineNotificationZeroConfChannel(p *testParams) { + alice := lntest.NewClnNode(p.h, p.m, "Alice") + alice.Start() + alice.Fund(10000000) + p.lsp.LightningNode().Fund(10000000) + + log.Print("Opening channel between Alice and the lsp") + channel := alice.OpenChannel(p.lsp.LightningNode(), &lntest.OpenChannelOptions{ + AmountSat: publicChanAmount, + IsPublic: true, + }) + channelId := alice.WaitForChannelReady(channel) + + log.Printf("Adding bob's invoices") + outerAmountMsat := uint64(2100000) + innerAmountMsat := calculateInnerAmountMsat(p.lsp, outerAmountMsat, nil) + description := "Please pay me" + innerInvoice, outerInvoice := GenerateInvoices(p.BreezClient(), + generateInvoicesRequest{ + innerAmountMsat: innerAmountMsat, + outerAmountMsat: outerAmountMsat, + description: description, + lsp: p.lsp, + }) + + log.Print("Connecting bob to lspd") + p.BreezClient().Node().ConnectPeer(p.lsp.LightningNode()) + p.BreezClient().SetHtlcAcceptor(innerAmountMsat) + + // TODO: Fix race waiting for htlc interceptor. + log.Printf("Waiting %v to allow htlc interceptor to activate.", htlcInterceptorDelay) + <-time.After(htlcInterceptorDelay) + + log.Printf("Registering payment with lsp") + RegisterPayment(p.lsp, &lspd.PaymentInformation{ + PaymentHash: innerInvoice.paymentHash, + PaymentSecret: innerInvoice.paymentSecret, + Destination: p.BreezClient().Node().NodeId(), + IncomingAmountMsat: int64(outerAmountMsat), + OutgoingAmountMsat: int64(innerAmountMsat), + }, false) + + expectedheight := p.Miner().GetBlockHeight() + log.Printf("Alice paying") + route := constructRoute(p.lsp.LightningNode(), p.BreezClient().Node(), channelId, lntest.NewShortChanIDFromString("1x0x0"), outerAmountMsat) + _, err := alice.PayViaRoute(outerAmountMsat, outerInvoice.paymentHash, outerInvoice.paymentSecret, route) + assert.Nil(p.t, err) + + <-time.After(time.Second * 2) + log.Printf("Adding bob's invoice for zero conf payment") + amountMsat := uint64(2100000) + + bobInvoice := p.BreezClient().Node().CreateBolt11Invoice(&lntest.CreateInvoiceOptions{ + AmountMsat: amountMsat, + IncludeHopHints: true, + }) + + invoiceWithHint := bobInvoice.Bolt11 + if !ContainsHopHint(p.t, bobInvoice.Bolt11) { + chans := p.BreezClient().Node().GetChannels() + assert.Len(p.t, chans, 1) + + var id lntest.ShortChannelID + if chans[0].RemoteAlias != nil { + id = *chans[0].RemoteAlias + } else if chans[0].LocalAlias != nil { + id = *chans[0].LocalAlias + } else { + id = chans[0].ShortChannelID + } + invoiceWithHint = AddHopHint(p.BreezClient(), bobInvoice.Bolt11, p.Lsp(), id, nil) + } + + log.Printf("Invoice with hint: %s", invoiceWithHint) + + // Kill the mobile client + log.Printf("Stopping breez client") + p.BreezClient().Stop() + p.BreezClient().ResetHtlcAcceptor() + + port, err := lntest.GetPort() + if err != nil { + assert.FailNow(p.t, "failed to get port for delivery service") + } + addr := fmt.Sprintf("127.0.0.1:%d", port) + delivered := make(chan struct{}) + + notify := newNotificationDeliveryService(addr, func(resp http.ResponseWriter, req *http.Request) { + var body PaymentReceivedPayload + err = json.NewDecoder(req.Body).Decode(&body) + assert.NoError(p.t, err) + + close(delivered) + }) + go notify.Start(p.h.Ctx) + go func() { + <-delivered + log.Printf("Starting breez client again") + p.BreezClient().Start() + p.BreezClient().Node().ConnectPeer(p.lsp.LightningNode()) + }() + + url := "http://" + addr + "/api/v1/notify" + first := sha256.Sum256([]byte(url)) + second := sha256.Sum256(first[:]) + sig, err := ecdsa.SignCompact(p.BreezClient().Node().PrivateKey(), second[:], true) + assert.NoError(p.t, err) + + p.lsp.NotificationsRpc().SubscribeNotifications(p.h.Ctx, ¬ifications.SubscribeNotificationsRequest{ + Url: url, + Signature: sig, + }) + + log.Printf("Alice paying zero conf invoice") + payResp := alice.Pay(invoiceWithHint) + invoiceResult := p.BreezClient().Node().GetInvoice(bobInvoice.PaymentHash) + + assert.Equal(p.t, payResp.PaymentPreimage, invoiceResult.PaymentPreimage) + assert.Equal(p.t, amountMsat, invoiceResult.AmountReceivedMsat) + + // Make sure we haven't accidentally mined blocks in between. + actualheight := p.Miner().GetBlockHeight() + assert.Equal(p.t, expectedheight, actualheight) +} From aa54cea1f334335129a23b06a00b14387f64551b Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Fri, 23 Jun 2023 10:50:05 +0200 Subject: [PATCH 203/214] reduce nesting in interceptor --- interceptor/intercept.go | 213 ++++++++++++++++++++++----------------- itest/probing_test.go | 4 +- 2 files changed, 124 insertions(+), 93 deletions(-) diff --git a/interceptor/intercept.go b/interceptor/intercept.go index cffa14c..90b2ec0 100644 --- a/interceptor/intercept.go +++ b/interceptor/intercept.go @@ -90,6 +90,11 @@ func (i *Interceptor) Intercept(scid *basetypes.ShortChannelID, reqPaymentHash [ } isRegistered := paymentSecret != nil + // Sanity check. If the payment is registered, the destination is always set. + if isRegistered && (destination == nil || len(destination) != 33) { + log.Printf("ERROR: Payment was registered without destination. paymentHash: %s", reqPaymentHashStr) + } + isProbe := isRegistered && !bytes.Equal(paymentHash, reqPaymentHash) nextHop, _ := i.client.GetPeerId(scid) if err != nil { @@ -101,121 +106,77 @@ func (i *Interceptor) Intercept(scid *basetypes.ShortChannelID, reqPaymentHash [ // If the payment was registered, but the next hop is not the destination // that means we are not the last hop of the payment, so we'll just forward. - if destination != nil && nextHop != nil && !bytes.Equal(nextHop, destination) { + if isRegistered && nextHop != nil && !bytes.Equal(nextHop, destination) { return InterceptResult{ Action: INTERCEPT_RESUME, }, nil } - // nextHop is set if the sender's scid corresponds to a known channel + // nextHop is set if the sender's scid corresponds to a known channel. // destination is set if the payment was registered for a channel open. - // The 'actual' next hop will be either of those. Or nil if the next hop - // is unknown. + // The 'actual' next hop will be either of those. if nextHop == nil { - nextHop = destination - } - if nextHop != nil { - isConnected, err := i.client.IsConnected(nextHop) - if err != nil { - log.Printf("IsConnected(%x) error: %v", nextHop, err) + // If the next hop cannot be deduced from the scid, and the payment + // is not registered, there's nothing left to be done. Just continue. + if !isRegistered { return InterceptResult{ - Action: INTERCEPT_FAIL_HTLC_WITH_CODE, - FailureCode: FAILURE_TEMPORARY_CHANNEL_FAILURE, + Action: INTERCEPT_RESUME, }, nil } + // The payment was registered, so the next hop is the registered + // destination + nextHop = destination + } + + isConnected, err := i.client.IsConnected(nextHop) + if err != nil { + log.Printf("IsConnected(%x) error: %v", nextHop, err) + return &InterceptResult{ + Action: INTERCEPT_FAIL_HTLC_WITH_CODE, + FailureCode: FAILURE_TEMPORARY_CHANNEL_FAILURE, + }, nil + } + + if isProbe { + // If this is a known probe, we'll quit early for non-connected clients. if !isConnected { - // If not connected, send a notification to the registered - // notification service for this client if available. - notified, err := i.notificationService.Notify( - hex.EncodeToString(nextHop), - reqPaymentHashStr, - ) + return InterceptResult{ + Action: INTERCEPT_RESUME, + }, nil + } - // If this errors or the client is not notified, the client - // is offline or unknown. We'll resume the HTLC (which will - // result in UNKOWN_NEXT_PEER) - if err != nil { - return InterceptResult{ - Action: INTERCEPT_RESUME, - }, nil - } - - if notified { - log.Printf("Notified %x of pending htlc", nextHop) - d, err := time.ParseDuration(i.config.NotificationTimeout) - if err != nil { - log.Printf("WARN: No NotificationTimeout set. Using default 1m") - d = time.Minute - } - timeout := time.Now().Add(d) - err = i.client.WaitOnline(nextHop, timeout) - - // If there's an error waiting, resume the htlc. It will - // probably fail with UNKNOWN_NEXT_PEER. - if err != nil { - log.Printf( - "waiting for peer %x to come online failed with %v", - nextHop, - err, - ) - return InterceptResult{ - Action: INTERCEPT_RESUME, - }, nil - } - - log.Printf("Peer %x is back online. Continue htlc.", nextHop) - // At this point we know a few things. - // - This is either a channel partner or a registered payment - // - they were offline - // - They got notified about the htlc - // - They came back online - // So if this payment was not registered, this is a channel - // partner and we have to wait for the channel to become active - // before we can forward. - if !isRegistered { - err = i.client.WaitChannelActive(nextHop, timeout) - if err != nil { - log.Printf( - "waiting for channnel with %x to become active failed with %v", - nextHop, - err, - ) - return InterceptResult{ - Action: INTERCEPT_RESUME, - }, nil - } - } - } else if isProbe { - return InterceptResult{ - Action: INTERCEPT_FAIL_HTLC_WITH_CODE, - FailureCode: FAILURE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS, - }, nil - } else { - // If we haven't notified, resume the htlc. It will probably - // fail with UNKNOWN_NEXT_PEER. - return InterceptResult{ - Action: INTERCEPT_RESUME, - }, nil - } + // If it's a probe, they are connected, but need a channel, we'll return + // INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS. This is an out-of-spec + // error code, because it shouldn't be returned by an intermediate + // node. But senders implementnig the probing-01: prefix should + // know that the actual payment would probably succeed. + if channelPoint == nil { + return InterceptResult{ + Action: INTERCEPT_FAIL_HTLC_WITH_CODE, + FailureCode: FAILURE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS, + }, nil } } + if !isConnected { + // Make sure the client is connected by potentially notifying them to come online. + notifyResult := i.notify(reqPaymentHashStr, nextHop, isRegistered) + if notifyResult != nil { + return *notifyResult, nil + } + } + + // The peer is online, we can resume the htlc if it's not a channel open. if !isRegistered { return InterceptResult{ Action: INTERCEPT_RESUME, }, nil } + // The first htlc of a MPP will open the channel. if channelPoint == nil { - if isProbe { - return InterceptResult{ - Action: INTERCEPT_FAIL_HTLC_WITH_CODE, - FailureCode: FAILURE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS, - }, nil - } - // TODO: When opening_fee_params is enforced, turn this check in a temporary channel failure. if params == nil { log.Printf("DEPRECATED: Intercepted htlc with deprecated fee mechanism. Using default fees. payment hash: %s", reqPaymentHashStr) @@ -228,6 +189,7 @@ func (i *Interceptor) Intercept(scid *basetypes.ShortChannelID, reqPaymentHash [ } } + // Make sure the cltv delta is enough. if int64(reqIncomingExpiry)-int64(reqOutgoingExpiry) < int64(i.config.TimeLockDelta) { return InterceptResult{ Action: INTERCEPT_FAIL_HTLC_WITH_CODE, @@ -244,6 +206,8 @@ func (i *Interceptor) Intercept(scid *basetypes.ShortChannelID, reqPaymentHash [ }, nil } + // Make sure the opening_fee_params are not expired. + // If they are expired, but the current chain fee is fine, open channel anyway. if time.Now().UTC().After(validUntil) { if !i.isCurrentChainFeeCheaper(token, params) { log.Printf("Intercepted expired payment registration. Failing payment. payment hash: %x, valid until: %s", paymentHash, params.ValidUntil) @@ -397,6 +361,73 @@ func (i *Interceptor) Intercept(scid *basetypes.ShortChannelID, reqPaymentHash [ return resp.(InterceptResult) } +func (i *Interceptor) notify(reqPaymentHashStr string, nextHop []byte, isRegistered bool) *InterceptResult { + // If not connected, send a notification to the registered + // notification service for this client if available. + notified, err := i.notificationService.Notify( + hex.EncodeToString(nextHop), + reqPaymentHashStr, + ) + + // If this errors or the client is not notified, the client + // is offline or unknown. We'll resume the HTLC (which will + // result in UNKOWN_NEXT_PEER) + if err != nil || !notified { + return &InterceptResult{ + Action: INTERCEPT_RESUME, + } + } + + log.Printf("Notified %x of pending htlc", nextHop) + d, err := time.ParseDuration(i.config.NotificationTimeout) + if err != nil { + log.Printf("WARN: No NotificationTimeout set. Using default 1m") + d = time.Minute + } + timeout := time.Now().Add(d) + + // Wait for a while to allow the client to come online. + err = i.client.WaitOnline(nextHop, timeout) + + // If there's an error waiting, resume the htlc. It will + // probably fail with UNKNOWN_NEXT_PEER. + if err != nil { + log.Printf( + "waiting for peer %x to come online failed with %v", + nextHop, + err, + ) + return &InterceptResult{ + Action: INTERCEPT_RESUME, + } + } + + log.Printf("Peer %x is back online. Continue htlc.", nextHop) + // At this point we know a few things. + // - This is either a channel partner or a registered payment + // - they were offline + // - They got notified about the htlc + // - They came back online + // So if this payment was not registered, this is a channel + // partner and we have to wait for the channel to become active + // before we can forward. + if !isRegistered { + err = i.client.WaitChannelActive(nextHop, timeout) + if err != nil { + log.Printf( + "waiting for channnel with %x to become active failed with %v", + nextHop, + err, + ) + return &InterceptResult{ + Action: INTERCEPT_RESUME, + } + } + } + + return nil +} + func (i *Interceptor) isCurrentChainFeeCheaper(token string, params *OpeningFeeParams) bool { settings, err := i.store.GetFeeParamsSettings(token) if err != nil { diff --git a/itest/probing_test.go b/itest/probing_test.go index 3352afe..d961f6c 100644 --- a/itest/probing_test.go +++ b/itest/probing_test.go @@ -70,6 +70,6 @@ func testProbing(p *testParams) { log.Printf("Alice paying with fake payment hash with Bob offline %x", fakePaymentHash) _, err = alice.PayViaRoute(outerAmountMsat, fakePaymentHash, outerInvoice.paymentSecret, route) - // Expect temporary channel failure if the peer is offline - assert.Contains(p.t, err.Error(), "WIRE_INCORRECT_OR_UNKNOWN_PAYMENT_DETAILS") + // Expect unknown next peer if the peer is offline + assert.Contains(p.t, err.Error(), "WIRE_UNKNOWN_NEXT_PEER") } From c37a7452f462529e07e78c8102a59fb6f1865657 Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Fri, 23 Jun 2023 11:30:40 +0200 Subject: [PATCH 204/214] Improve cln IsConnected --- cln/cln_client.go | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/cln/cln_client.go b/cln/cln_client.go index c81674d..6bb6bbb 100644 --- a/cln/cln_client.go +++ b/cln/cln_client.go @@ -5,6 +5,7 @@ import ( "fmt" "log" "path/filepath" + "strings" "time" "github.com/breez/lspd/basetypes" @@ -59,17 +60,19 @@ func (c *ClnClient) GetInfo() (*lightning.GetInfoResult, error) { func (c *ClnClient) IsConnected(destination []byte) (bool, error) { pubKey := hex.EncodeToString(destination) - peers, err := c.client.ListPeers() + peer, err := c.client.GetPeer(pubKey) if err != nil { - log.Printf("CLN: client.ListPeers() error: %v", err) - return false, fmt.Errorf("CLN: client.ListPeers() error: %w", err) + if strings.Contains(err.Error(), "not found") { + return false, nil + } + + log.Printf("CLN: client.GetPeer(%v) error: %v", pubKey, err) + return false, fmt.Errorf("CLN: client.GetPeer(%v) error: %w", pubKey, err) } - for _, peer := range peers { - if pubKey == peer.Id && peer.Connected { - log.Printf("destination online: %x", destination) - return true, nil - } + if peer.Connected { + log.Printf("CLN: destination online: %x", destination) + return true, nil } log.Printf("CLN: destination offline: %x", destination) From 34646d50a5917466527d73aae0856fbfcdde1455 Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Fri, 30 Jun 2023 19:50:15 +0200 Subject: [PATCH 205/214] move lnd specific onion creation to lnd --- interceptor/intercept.go | 105 ++++++--------------------------------- lnd/interceptor.go | 92 +++++++++++++++++++++++++++++++--- 2 files changed, 100 insertions(+), 97 deletions(-) diff --git a/interceptor/intercept.go b/interceptor/intercept.go index 90b2ec0..9bbb0cd 100644 --- a/interceptor/intercept.go +++ b/interceptor/intercept.go @@ -14,12 +14,7 @@ import ( "github.com/breez/lspd/config" "github.com/breez/lspd/lightning" "github.com/breez/lspd/notifications" - "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/wire" - sphinx "github.com/lightningnetwork/lightning-onion" - "github.com/lightningnetwork/lnd/lnwire" - "github.com/lightningnetwork/lnd/record" - "github.com/lightningnetwork/lnd/routing/route" "golang.org/x/sync/singleflight" ) @@ -40,13 +35,14 @@ var ( ) type InterceptResult struct { - Action InterceptAction - FailureCode InterceptFailureCode - Destination []byte - AmountMsat uint64 - ChannelPoint *wire.OutPoint - ChannelId uint64 - OnionBlob []byte + Action InterceptAction + FailureCode InterceptFailureCode + Destination []byte + AmountMsat uint64 + TotalAmountMsat uint64 + ChannelPoint *wire.OutPoint + ChannelId uint64 + PaymentSecret []byte } type Interceptor struct { @@ -230,81 +226,9 @@ func (i *Interceptor) Intercept(scid *basetypes.ShortChannelID, reqPaymentHash [ } } - pubKey, err := btcec.ParsePubKey(destination) - if err != nil { - log.Printf("btcec.ParsePubKey(%x): %v", destination, err) - return InterceptResult{ - Action: INTERCEPT_FAIL_HTLC_WITH_CODE, - FailureCode: FAILURE_TEMPORARY_CHANNEL_FAILURE, - }, nil - } - - sessionKey, err := btcec.NewPrivateKey() - if err != nil { - log.Printf("btcec.NewPrivateKey(): %v", err) - return InterceptResult{ - Action: INTERCEPT_FAIL_HTLC_WITH_CODE, - FailureCode: FAILURE_TEMPORARY_CHANNEL_FAILURE, - }, nil - } - var bigProd, bigAmt big.Int amt := (bigAmt.Div(bigProd.Mul(big.NewInt(outgoingAmountMsat), big.NewInt(int64(reqOutgoingAmountMsat))), big.NewInt(incomingAmountMsat))).Int64() - var addr [32]byte - copy(addr[:], paymentSecret) - hop := route.Hop{ - AmtToForward: lnwire.MilliSatoshi(amt), - OutgoingTimeLock: reqOutgoingExpiry, - MPP: record.NewMPP(lnwire.MilliSatoshi(outgoingAmountMsat), addr), - CustomRecords: make(record.CustomSet), - } - - var b bytes.Buffer - err = hop.PackHopPayload(&b, uint64(0)) - if err != nil { - log.Printf("hop.PackHopPayload(): %v", err) - return InterceptResult{ - Action: INTERCEPT_FAIL_HTLC_WITH_CODE, - FailureCode: FAILURE_TEMPORARY_CHANNEL_FAILURE, - }, nil - } - - payload, err := sphinx.NewHopPayload(nil, b.Bytes()) - if err != nil { - log.Printf("sphinx.NewHopPayload(): %v", err) - return InterceptResult{ - Action: INTERCEPT_FAIL_HTLC_WITH_CODE, - FailureCode: FAILURE_TEMPORARY_CHANNEL_FAILURE, - }, nil - } - - var sphinxPath sphinx.PaymentPath - sphinxPath[0] = sphinx.OnionHop{ - NodePub: *pubKey, - HopPayload: payload, - } - sphinxPacket, err := sphinx.NewOnionPacket( - &sphinxPath, sessionKey, reqPaymentHash, - sphinx.DeterministicPacketFiller, - ) - if err != nil { - log.Printf("sphinx.NewOnionPacket(): %v", err) - return InterceptResult{ - Action: INTERCEPT_FAIL_HTLC_WITH_CODE, - FailureCode: FAILURE_TEMPORARY_CHANNEL_FAILURE, - }, nil - } - var onionBlob bytes.Buffer - err = sphinxPacket.Encode(&onionBlob) - if err != nil { - log.Printf("sphinxPacket.Encode(): %v", err) - return InterceptResult{ - Action: INTERCEPT_FAIL_HTLC_WITH_CODE, - FailureCode: FAILURE_TEMPORARY_CHANNEL_FAILURE, - }, nil - } - deadline := time.Now().Add(60 * time.Second) for { @@ -334,12 +258,13 @@ func (i *Interceptor) Intercept(scid *basetypes.ShortChannelID, reqPaymentHash [ } return InterceptResult{ - Action: INTERCEPT_RESUME_WITH_ONION, - Destination: destination, - ChannelPoint: channelPoint, - ChannelId: channelID, - AmountMsat: uint64(amt), - OnionBlob: onionBlob.Bytes(), + Action: INTERCEPT_RESUME_WITH_ONION, + Destination: destination, + ChannelPoint: channelPoint, + ChannelId: channelID, + PaymentSecret: paymentSecret, + AmountMsat: uint64(amt), + TotalAmountMsat: uint64(outgoingAmountMsat), }, nil } diff --git a/lnd/interceptor.go b/lnd/interceptor.go index fa296d0..8d1574f 100644 --- a/lnd/interceptor.go +++ b/lnd/interceptor.go @@ -1,6 +1,7 @@ package lnd import ( + "bytes" "context" "log" "sync" @@ -9,8 +10,13 @@ import ( "github.com/breez/lspd/basetypes" "github.com/breez/lspd/config" "github.com/breez/lspd/interceptor" + "github.com/btcsuite/btcd/btcec/v2" + sphinx "github.com/lightningnetwork/lightning-onion" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/routerrpc" + "github.com/lightningnetwork/lnd/lnwire" + "github.com/lightningnetwork/lnd/record" + "github.com/lightningnetwork/lnd/routing/route" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) @@ -133,13 +139,23 @@ func (i *LndHtlcInterceptor) intercept() error { interceptResult := i.interceptor.Intercept(&scid, request.PaymentHash, request.OutgoingAmountMsat, request.OutgoingExpiry, request.IncomingExpiry) switch interceptResult.Action { case interceptor.INTERCEPT_RESUME_WITH_ONION: - interceptorClient.Send(&routerrpc.ForwardHtlcInterceptResponse{ - IncomingCircuitKey: request.IncomingCircuitKey, - Action: routerrpc.ResolveHoldForwardAction_RESUME, - OutgoingAmountMsat: interceptResult.AmountMsat, - OutgoingRequestedChanId: uint64(interceptResult.ChannelId), - OnionBlob: interceptResult.OnionBlob, - }) + onion, err := i.constructOnion(interceptResult, request.OutgoingExpiry, request.PaymentHash) + if err == nil { + interceptorClient.Send(&routerrpc.ForwardHtlcInterceptResponse{ + IncomingCircuitKey: request.IncomingCircuitKey, + Action: routerrpc.ResolveHoldForwardAction_RESUME, + OutgoingAmountMsat: interceptResult.AmountMsat, + OutgoingRequestedChanId: uint64(interceptResult.ChannelId), + OnionBlob: onion, + }) + } else { + interceptorClient.Send(&routerrpc.ForwardHtlcInterceptResponse{ + IncomingCircuitKey: request.IncomingCircuitKey, + Action: routerrpc.ResolveHoldForwardAction_FAIL, + FailureCode: lnrpc.Failure_TEMPORARY_CHANNEL_FAILURE, + }) + } + case interceptor.INTERCEPT_FAIL_HTLC_WITH_CODE: interceptorClient.Send(&routerrpc.ForwardHtlcInterceptResponse{ IncomingCircuitKey: request.IncomingCircuitKey, @@ -179,3 +195,65 @@ func (i *LndHtlcInterceptor) mapFailureCode(original interceptor.InterceptFailur return lnrpc.Failure_TEMPORARY_CHANNEL_FAILURE } } + +func (i *LndHtlcInterceptor) constructOnion( + interceptResult interceptor.InterceptResult, + reqOutgoingExpiry uint32, + reqPaymentHash []byte, +) ([]byte, error) { + pubKey, err := btcec.ParsePubKey(interceptResult.Destination) + if err != nil { + log.Printf("btcec.ParsePubKey(%x): %v", interceptResult.Destination, err) + return nil, err + } + + sessionKey, err := btcec.NewPrivateKey() + if err != nil { + log.Printf("btcec.NewPrivateKey(): %v", err) + return nil, err + } + + var addr [32]byte + copy(addr[:], interceptResult.PaymentSecret) + hop := route.Hop{ + AmtToForward: lnwire.MilliSatoshi(interceptResult.AmountMsat), + OutgoingTimeLock: reqOutgoingExpiry, + MPP: record.NewMPP(lnwire.MilliSatoshi(interceptResult.TotalAmountMsat), addr), + CustomRecords: make(record.CustomSet), + } + + var b bytes.Buffer + err = hop.PackHopPayload(&b, uint64(0)) + if err != nil { + log.Printf("hop.PackHopPayload(): %v", err) + return nil, err + } + + payload, err := sphinx.NewHopPayload(nil, b.Bytes()) + if err != nil { + log.Printf("sphinx.NewHopPayload(): %v", err) + return nil, err + } + + var sphinxPath sphinx.PaymentPath + sphinxPath[0] = sphinx.OnionHop{ + NodePub: *pubKey, + HopPayload: payload, + } + sphinxPacket, err := sphinx.NewOnionPacket( + &sphinxPath, sessionKey, reqPaymentHash, + sphinx.DeterministicPacketFiller, + ) + if err != nil { + log.Printf("sphinx.NewOnionPacket(): %v", err) + return nil, err + } + var onionBlob bytes.Buffer + err = sphinxPacket.Encode(&onionBlob) + if err != nil { + log.Printf("sphinxPacket.Encode(): %v", err) + return nil, err + } + + return onionBlob.Bytes(), nil +} From 70d6afcf85286f831959ed3c18ece246f25d9085 Mon Sep 17 00:00:00 2001 From: Yaacov Akiba Slama Date: Sat, 1 Jul 2023 22:39:07 +0300 Subject: [PATCH 206/214] Add cln channel acceptor --- cln_plugin/channel_acceptor.go | 37 +++++++++ cln_plugin/cln_plugin.go | 148 ++++++++++++++++++++++++++++++--- go.mod | 1 + 3 files changed, 176 insertions(+), 10 deletions(-) create mode 100644 cln_plugin/channel_acceptor.go diff --git a/cln_plugin/channel_acceptor.go b/cln_plugin/channel_acceptor.go new file mode 100644 index 0000000..848dc60 --- /dev/null +++ b/cln_plugin/channel_acceptor.go @@ -0,0 +1,37 @@ +package cln_plugin + +import ( + "encoding/json" + "fmt" + + sj "go.starlark.net/lib/json" + "go.starlark.net/starlark" +) + +func channelAcceptor(acceptScript string, method string, openChannel json.RawMessage) (json.RawMessage, error) { + reject, _ := json.Marshal(struct { + Result string `json:"result"` + }{Result: "reject"}) + + sd := starlark.StringDict{ + "method": starlark.String(method), + "openchannel": starlark.String(openChannel), + } + for _, k := range sj.Module.Members.Keys() { + sd[k] = sj.Module.Members[k] + } + value, err := starlark.Eval( + &starlark.Thread{}, + "", + acceptScript, + sd, + ) + if err != nil { + return reject, err + } + s, ok := value.(starlark.String) + if !ok { + return reject, fmt.Errorf("not a string") + } + return json.RawMessage(s.GoString()), nil +} diff --git a/cln_plugin/cln_plugin.go b/cln_plugin/cln_plugin.go index 7bbfe86..36a6c21 100644 --- a/cln_plugin/cln_plugin.go +++ b/cln_plugin/cln_plugin.go @@ -19,6 +19,7 @@ import ( const ( SubscriberTimeoutOption = "lsp-subscribertimeout" ListenAddressOption = "lsp-listen" + channelAcceptScript = "lsp-channel-accept-script" ) var ( @@ -43,11 +44,12 @@ var ( ) type ClnPlugin struct { - done chan struct{} - server *server - in *os.File - out *bufio.Writer - writeMtx sync.Mutex + done chan struct{} + server *server + in *os.File + out *bufio.Writer + writeMtx sync.Mutex + channelAcceptScript string } func NewClnPlugin(in, out *os.File) *ClnPlugin { @@ -210,6 +212,20 @@ func (c *ClnPlugin) processRequest(request *Request) { c.handleShutdown(request) case "htlc_accepted": c.handleHtlcAccepted(request) + case "openchannel": + // handle open channel in a goroutine, because order doesn't matter. + go c.handleOpenChannel(request) + case "openchannel2": + // handle open channel in a goroutine, because order doesn't matter. + go c.handleOpenChannel(request) + case "getchannelacceptscript": + c.sendToCln(&Response{ + JsonRpc: SpecVersion, + Id: request.Id, + Result: c.channelAcceptScript, + }) + case "setchannelacceptscript": + c.handleSetChannelAcceptScript(request) default: c.sendError( request.Id, @@ -239,14 +255,28 @@ func (c *ClnPlugin) handleGetManifest(request *Request) { "if no subscriber is active. golang duration string.", Default: &DefaultSubscriberTimeout, }, - }, - RpcMethods: []*RpcMethod{}, - Dynamic: true, - Hooks: []Hook{ { - Name: "htlc_accepted", + Name: channelAcceptScript, + Type: "string", + Description: "starlark script for channel acceptor.", }, }, + RpcMethods: []*RpcMethod{ + { + Name: "getchannelacceptscript", + Description: "Get the startlark channel acceptor script", + }, + { + Name: "setchannelacceptscript", + Description: "Set the startlark channel acceptor script", + }, + }, + Dynamic: true, + Hooks: []Hook{ + {Name: "htlc_accepted"}, + {Name: "openchannel"}, + {Name: "openchannel2"}, + }, NonNumericIds: true, Subscriptions: []string{ "shutdown", @@ -270,6 +300,31 @@ func (c *ClnPlugin) handleInit(request *Request) { return } + // Get the channel acceptor script option. + sc, ok := initMsg.Options[channelAcceptScript] + if !ok { + c.sendError( + request.Id, + InvalidParams, + fmt.Sprintf("Missing option '%s'", channelAcceptScript), + ) + return + } + + c.channelAcceptScript, ok = sc.(string) + if !ok { + c.sendError( + request.Id, + InvalidParams, + fmt.Sprintf( + "Invalid value '%v' for option '%s'", + sc, + channelAcceptScript, + ), + ) + return + } + // Get the listen address option. l, ok := initMsg.Options[ListenAddressOption] if !ok { @@ -382,6 +437,79 @@ func (c *ClnPlugin) handleHtlcAccepted(request *Request) { c.server.Send(idToString(request.Id), &htlc) } +func (c *ClnPlugin) handleSetChannelAcceptScript(request *Request) { + var params []string + err := json.Unmarshal(request.Params, ¶ms) + if err != nil { + c.sendError( + request.Id, + ParseError, + fmt.Sprintf( + "Failed to unmarshal setchannelacceptscript params:%s [%s]", + err.Error(), + request.Params, + ), + ) + return + } + if len(params) >= 1 { + c.channelAcceptScript = params[0] + } + c.sendToCln(&Response{ + JsonRpc: SpecVersion, + Id: request.Id, + Result: c.channelAcceptScript, + }) +} + +func unmarshalOpenChannel(request *Request) (r json.RawMessage, err error) { + switch request.Method { + case "openchannel": + var openChannel struct { + OpenChannel json.RawMessage `json:"openchannel"` + } + err = json.Unmarshal(request.Params, &openChannel) + if err != nil { + return + } + r = openChannel.OpenChannel + case "openchannel2": + var openChannel struct { + OpenChannel json.RawMessage `json:"openchannel2"` + } + err = json.Unmarshal(request.Params, &openChannel) + if err != nil { + return + } + r = openChannel.OpenChannel + } + return r, nil +} +func (c *ClnPlugin) handleOpenChannel(request *Request) { + p, err := unmarshalOpenChannel(request) + if err != nil { + c.sendError( + request.Id, + ParseError, + fmt.Sprintf( + "Failed to unmarshal openchannel params:%s [%s]", + err.Error(), + request.Params, + ), + ) + return + } + result, err := channelAcceptor(c.channelAcceptScript, request.Method, p) + if err != nil { + log.Printf("channelAcceptor error - request: %s error: %v", request, err) + } + c.sendToCln(&Response{ + JsonRpc: SpecVersion, + Id: request.Id, + Result: result, + }) +} + // Sends an error to cln. func (c *ClnPlugin) sendError(id json.RawMessage, code int, message string) { // Log the error to cln first. diff --git a/go.mod b/go.mod index d46b271..14a06e6 100644 --- a/go.mod +++ b/go.mod @@ -21,6 +21,7 @@ require ( github.com/lightningnetwork/lnd/tlv v1.1.0 github.com/niftynei/glightning v0.8.2 github.com/stretchr/testify v1.8.1 + go.starlark.net v0.0.0-20230612165344-9532f5667272 golang.org/x/exp v0.0.0-20230321023759-10a507213a29 golang.org/x/sync v0.1.0 google.golang.org/grpc v1.50.1 From c7eeaf1cbef9041b60e260e0739bad9ea1e91769 Mon Sep 17 00:00:00 2001 From: lndev Date: Wed, 12 Jul 2023 09:06:51 +0000 Subject: [PATCH 207/214] Add default channel acceptor - Implemented a default channel acceptor script for the channel acceptor in 'channel_acceptor.go'. If no accept script is provided, the default script is set to "continue". - Updated 'cln_plugin.go' to include a new DefaultChannelAcceptorScript variable with an empty string default value. - Updated the handleGetManifest function to include DefaultChannelAcceptorScript as a default for the 'channelAcceptScript' field. --- cln_plugin/channel_acceptor.go | 7 +++++++ cln_plugin/cln_plugin.go | 4 +++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/cln_plugin/channel_acceptor.go b/cln_plugin/channel_acceptor.go index 848dc60..8bb969e 100644 --- a/cln_plugin/channel_acceptor.go +++ b/cln_plugin/channel_acceptor.go @@ -12,6 +12,13 @@ func channelAcceptor(acceptScript string, method string, openChannel json.RawMes reject, _ := json.Marshal(struct { Result string `json:"result"` }{Result: "reject"}) + accept, _ := json.Marshal(struct { + Result string `json:"result"` + }{Result: "continue"}) + + if acceptScript == "" { + return accept, nil + } sd := starlark.StringDict{ "method": starlark.String(method), diff --git a/cln_plugin/cln_plugin.go b/cln_plugin/cln_plugin.go index 36a6c21..778fa33 100644 --- a/cln_plugin/cln_plugin.go +++ b/cln_plugin/cln_plugin.go @@ -23,7 +23,8 @@ const ( ) var ( - DefaultSubscriberTimeout = "1m" + DefaultSubscriberTimeout = "1m" + DefaultChannelAcceptorScript = "" ) const ( @@ -259,6 +260,7 @@ func (c *ClnPlugin) handleGetManifest(request *Request) { Name: channelAcceptScript, Type: "string", Description: "starlark script for channel acceptor.", + Default: &DefaultChannelAcceptorScript, }, }, RpcMethods: []*RpcMethod{ From 36f7b87b2953a2b949f665a3e4d2361667607477 Mon Sep 17 00:00:00 2001 From: lndev Date: Wed, 12 Jul 2023 09:06:44 +0000 Subject: [PATCH 208/214] Add GitHub Actions for Bitcoin, c-lightning, and LND setup and integration tests - A new GitHub Actions workflow for running integration tests has been added. This workflow is defined in .github/workflows/integration_tests.yaml. - New GitHub Actions for setting up Bitcoin, c-lightning, and LND environments have been implemented. - The actions use caching for quick builds. - The .gitignore file has been updated. - LND and CLN setup and tests now run in their own individual jobs, which should improve the workflow speed. - Introduced a new reusable action for handling test states. --- .github/actions/build-lspd/action.yaml | 23 +++ .../actions/process-test-state/action.yaml | 36 +++++ .github/actions/setup-bitcoin/action.yaml | 42 +++++ .github/actions/setup-clightning/action.yaml | 109 +++++++++++++ .github/actions/setup-itest/action.yaml | 16 ++ .github/actions/setup-lnd-client/action.yaml | 45 ++++++ .github/actions/setup-lnd-lsp/action.yaml | 45 ++++++ .github/actions/test-lspd/action.yaml | 144 ++++++++++++++++++ .github/workflows/integration_tests.yaml | 123 +++++++++++++++ .gitignore | 2 + 10 files changed, 585 insertions(+) create mode 100644 .github/actions/build-lspd/action.yaml create mode 100644 .github/actions/process-test-state/action.yaml create mode 100644 .github/actions/setup-bitcoin/action.yaml create mode 100644 .github/actions/setup-clightning/action.yaml create mode 100644 .github/actions/setup-itest/action.yaml create mode 100644 .github/actions/setup-lnd-client/action.yaml create mode 100644 .github/actions/setup-lnd-lsp/action.yaml create mode 100644 .github/actions/test-lspd/action.yaml create mode 100644 .github/workflows/integration_tests.yaml diff --git a/.github/actions/build-lspd/action.yaml b/.github/actions/build-lspd/action.yaml new file mode 100644 index 0000000..4e8d9c1 --- /dev/null +++ b/.github/actions/build-lspd/action.yaml @@ -0,0 +1,23 @@ +name: 'Build LSPD' +description: 'Build LSPD and upload the build artifacts.' +runs: + using: 'composite' + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Build LSPD + run: | + go get github.com/breez/lspd + go get github.com/breez/lspd/cln_plugin + go build . + go build -o lspd_plugin ./cln_plugin/cmd + shell: bash + + - name: Upload build artifacts + uses: actions/upload-artifact@v3 + with: + name: build-artifacts + path: | + ./lspd + ./lspd_plugin diff --git a/.github/actions/process-test-state/action.yaml b/.github/actions/process-test-state/action.yaml new file mode 100644 index 0000000..66cd1f9 --- /dev/null +++ b/.github/actions/process-test-state/action.yaml @@ -0,0 +1,36 @@ +name: "Process Test State" +description: "Check, tar and upload test state" +inputs: + artifact-name: + description: "Name of the artifact" + required: true + default: "test_state_artifact" + test-state-path: + description: "Path of the test state directory" + required: true + default: "/home/runner/test_state" +runs: + using: "composite" + steps: + - name: Check if test_state directory exists + id: check-test-state + run: | + if [ -d "${{ inputs.test-state-path }}" ]; then + echo "exists=true" >> $GITHUB_ENV + else + echo "exists=false" >> $GITHUB_ENV + fi + shell: bash + + - name: Tar state + run: | + find ${{ inputs.test-state-path }} -type f -o -type d | tar -czf ${{ inputs.test-state-path }}.tar.gz -T - + shell: bash + if: env.exists == 'true' + + - name: Upload test_state as artifact + uses: actions/upload-artifact@v3 + with: + name: ${{ inputs.artifact-name }} + path: ${{ inputs.test-state-path }}.tar.gz + if: env.exists == 'true' diff --git a/.github/actions/setup-bitcoin/action.yaml b/.github/actions/setup-bitcoin/action.yaml new file mode 100644 index 0000000..f07d87a --- /dev/null +++ b/.github/actions/setup-bitcoin/action.yaml @@ -0,0 +1,42 @@ +name: 'Setup Bitcoin Core' +description: 'Download and install Bitcoin Core' +inputs: + bitcoin-version: + description: 'Version of Bitcoin Core' + required: true +runs: + using: 'composite' + steps: + - name: Cache Bitcoin Core + id: cache-bitcoin + uses: actions/cache@v3 + with: + path: | + ~/bitcoin-core-${{ inputs.bitcoin-version }}/bitcoin-${{ inputs.bitcoin-version }}/bin/bitcoind + ~/bitcoin-core-${{ inputs.bitcoin-version }}/bitcoin-${{ inputs.bitcoin-version }}/bin/bitcoin-cli + key: bitcoin-core-${{ inputs.bitcoin-version }} + + - name: Setup dependencies + if: steps.cache-bitcoin.outputs.cache-hit != 'true' + run: | + sudo apt-get update + sudo apt-get install -y axel + shell: bash + + - name: Download and install Bitcoin Core + if: steps.cache-bitcoin.outputs.cache-hit != 'true' + run: | + mkdir -p ~/bitcoin-core-${{ inputs.bitcoin-version }} + cd ~/bitcoin-core-${{ inputs.bitcoin-version }} + axel https://bitcoin.org/bin/bitcoin-core-${{ inputs.bitcoin-version }}/bitcoin-${{ inputs.bitcoin-version }}-x86_64-linux-gnu.tar.gz + tar -xzf bitcoin-${{ inputs.bitcoin-version }}-x86_64-linux-gnu.tar.gz + rm bitcoin-${{ inputs.bitcoin-version }}-x86_64-linux-gnu.tar.gz + sudo cp ~/bitcoin-core-${{ inputs.bitcoin-version }}/bitcoin-${{ inputs.bitcoin-version }}/bin/bitcoind /usr/bin/ + sudo cp ~/bitcoin-core-${{ inputs.bitcoin-version }}/bitcoin-${{ inputs.bitcoin-version }}/bin/bitcoin-cli /usr/bin/ + shell: bash + + - name: Copy Binaries + run: | + sudo cp ~/bitcoin-core-${{ inputs.bitcoin-version }}/bitcoin-${{ inputs.bitcoin-version }}/bin/bitcoind /usr/bin/ + sudo cp ~/bitcoin-core-${{ inputs.bitcoin-version }}/bitcoin-${{ inputs.bitcoin-version }}/bin/bitcoin-cli /usr/bin/ + shell: bash diff --git a/.github/actions/setup-clightning/action.yaml b/.github/actions/setup-clightning/action.yaml new file mode 100644 index 0000000..fe4f5f1 --- /dev/null +++ b/.github/actions/setup-clightning/action.yaml @@ -0,0 +1,109 @@ +name: 'Setup Core Lightning' +description: 'Set up Core Lightning on the runner' + +inputs: + checkout-version: + description: 'v23.05.1' + required: true + default: 'v23.05.1' + +runs: + using: 'composite' + steps: + - name: Cache Core Lightning + id: cache-core-lightning + uses: actions/cache@v3 + with: + path: | + lightning_git/lightningd/lightning_hsmd + lightning_git/lightningd/lightning_gossipd + lightning_git/lightningd/lightning_openingd + lightning_git/lightningd/lightning_dualopend + lightning_git/lightningd/lightning_channeld + lightning_git/lightningd/lightning_closingd + lightning_git/lightningd/lightning_onchaind + lightning_git/lightningd/lightning_connectd + lightning_git/lightningd/lightning_websocketd + lightning_git/lightningd/lightningd + lightning_git/plugins/offers + lightning_git/plugins/topology + lightning_git/plugins/spenderp + lightning_git/plugins/test/run-route-overlong + lightning_git/plugins/test/run-funder_policy + lightning_git/plugins/pay + lightning_git/plugins/bkpr/test/run-bkpr_db + lightning_git/plugins/bkpr/test/run-recorder + lightning_git/plugins/funder + lightning_git/plugins/bookkeeper + lightning_git/plugins/txprepare + lightning_git/plugins/keysend + lightning_git/plugins/fetchinvoice + lightning_git/plugins/bcli + lightning_git/plugins/cln-grpc + lightning_git/plugins/commando + lightning_git/plugins/autoclean + lightning_git/plugins/chanbackup + lightning_git/plugins/sql + lightning_git/cli/lightning-cli + lightning_git/devtools/bolt11-cli + lightning_git/devtools/decodemsg + lightning_git/devtools/onion + lightning_git/devtools/dump-gossipstore + lightning_git/devtools/gossipwith + lightning_git/devtools/create-gossipstore + lightning_git/devtools/mkcommit + lightning_git/devtools/mkfunding + lightning_git/devtools/mkclose + lightning_git/devtools/mkgossip + key: core-lightning-${{ inputs.checkout-version }} + + - name: Setup Python 3.8 + if: steps.cache-core-lightning.outputs.cache-hit != 'true' + uses: actions/setup-python@v4 + with: + python-version: 3.8 + + - name: Setup Rust + if: steps.cache-core-lightning.outputs.cache-hit != 'true' + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + profile: minimal + override: true + + - name: Install dependencies + if: steps.cache-core-lightning.outputs.cache-hit != 'true' + run: | + sudo apt-get install -y autoconf automake build-essential git libtool libgmp-dev libsqlite3-dev python3 python3-pip net-tools zlib1g-dev libsodium-dev gettext valgrind libpq-dev shellcheck cppcheck libsecp256k1-dev jq + sudo apt-get remove -y protobuf-compiler + curl -OL https://github.com/protocolbuffers/protobuf/releases/download/v3.12.0/protoc-3.12.0-linux-x86_64.zip + sudo unzip -o protoc-3.12.0-linux-x86_64.zip -d /usr/local bin/protoc + sudo unzip -o protoc-3.12.0-linux-x86_64.zip -d /usr/local 'include/*' + rm -f protoc-3.12.0-linux-x86_64.zip + sudo chmod 755 /usr/local/bin/protoc + shell: bash + + - name: Install Python dependencies + if: steps.cache-core-lightning.outputs.cache-hit != 'true' + run: | + pip3 install --upgrade pip + pip3 install poetry mako + pip install grpcio-tools + shell: bash + + - name: Checkout and build lightning + if: steps.cache-core-lightning.outputs.cache-hit != 'true' + uses: actions/checkout@v3 + with: + repository: ElementsProject/lightning + ref: ${{ inputs.checkout-version }} + path: lightning_git + + - name: Build Lightning + if: steps.cache-core-lightning.outputs.cache-hit != 'true' + run: | + cd lightning_git + ./configure --enable-developer --enable-rust + poetry install + poetry run make -j `nproc` + shell: bash diff --git a/.github/actions/setup-itest/action.yaml b/.github/actions/setup-itest/action.yaml new file mode 100644 index 0000000..43c8504 --- /dev/null +++ b/.github/actions/setup-itest/action.yaml @@ -0,0 +1,16 @@ +name: 'Cache itest' +description: 'Fetch LSPD Integration Test and cache the go directory' +runs: + using: 'composite' + steps: + - name: Cache itest + id: cache-itest + uses: actions/cache@v3 + with: + path: | + ~/go + key: itest + - name: Get LSPD Integration Test + if: steps.cache-itest.outputs.cache-hit != 'true' + run: go get github.com/breez/lspd/itest + shell: bash \ No newline at end of file diff --git a/.github/actions/setup-lnd-client/action.yaml b/.github/actions/setup-lnd-client/action.yaml new file mode 100644 index 0000000..accbbb6 --- /dev/null +++ b/.github/actions/setup-lnd-client/action.yaml @@ -0,0 +1,45 @@ +name: 'Setup LND Client' +description: 'Set up LND for the Client on the runner' + +inputs: + client-ref: + description: 'The Git reference for the Client version of LND' + required: true + default: 'v0.16.2-breez' + + go-version: + description: 'The Go version for building LND' + required: true + default: ^1.19 + +runs: + using: 'composite' + steps: + - name: Cache LND client + id: cache-lnd-client + uses: actions/cache@v3 + with: + path: | + ~/go_lnd_client/bin/lnd + key: go_lnd_client-${{ inputs.client-ref }}-${{ inputs.go-version }} + + - name: Set up Go 1.x + if: steps.cache-lnd-client.outputs.cache-hit != 'true' + uses: actions/setup-go@v4 + with: + go-version: ${{ inputs.go-version }} + + - name: Checkout LND for Client + if: steps.cache-lnd-client.outputs.cache-hit != 'true' + uses: actions/checkout@v3 + with: + repository: breez/lnd + ref: ${{ inputs.client-ref }} + path: lnd_client + + - name: Build LND for Client + if: steps.cache-lnd-client.outputs.cache-hit != 'true' + run: | + cd lnd_client + env GOPATH=~/go_lnd_client make install + shell: bash diff --git a/.github/actions/setup-lnd-lsp/action.yaml b/.github/actions/setup-lnd-lsp/action.yaml new file mode 100644 index 0000000..8074010 --- /dev/null +++ b/.github/actions/setup-lnd-lsp/action.yaml @@ -0,0 +1,45 @@ +name: 'Setup LND LSP' +description: 'Set up LND for LSP on the runner' + +inputs: + lsp-ref: + description: 'The Git reference for the LSP version of LND' + required: true + default: 'breez-node-v0.16.2-beta' + + go-version: + description: 'The Go version for building LND' + required: true + default: ^1.19 + +runs: + using: 'composite' + steps: + - name: Cache LND LSP + id: cache-lnd-lsp + uses: actions/cache@v3 + with: + path: | + ~/go_lnd_lsp/bin/lnd + key: go_lnd_lsp-${{ inputs.lsp-ref }}-${{ inputs.go-version }} + + - name: Set up Go 1.x + if: steps.cache-lnd-lsp.outputs.cache-hit != 'true' + uses: actions/setup-go@v4 + with: + go-version: ${{ inputs.go-version }} + + - name: Checkout LND for LSP + if: steps.cache-lnd-lsp.outputs.cache-hit != 'true' + uses: actions/checkout@v3 + with: + repository: breez/lnd + ref: ${{ inputs.lsp-ref }} + path: lnd_lsp + + - name: Build LND for LSP + if: steps.cache-lnd-lsp.outputs.cache-hit != 'true' + run: | + cd lnd_lsp + env GOPATH=~/go_lnd_lsp make install tags='submarineswaprpc chanreservedynamic routerrpc walletrpc chainrpc signrpc invoicesrpc' + shell: bash diff --git a/.github/actions/test-lspd/action.yaml b/.github/actions/test-lspd/action.yaml new file mode 100644 index 0000000..bd09fe8 --- /dev/null +++ b/.github/actions/test-lspd/action.yaml @@ -0,0 +1,144 @@ +name: 'Test LSPD' +description: 'Downloads artifacts, sets permissions, caches and runs a specified test and processes the result as an artifact.' +inputs: + TESTRE: + description: 'Test regular expression to run.' + required: true + artifact-name: + description: 'Artifact name for the test state.' + required: true + bitcoin-version: + description: 'Bitcoin version.' + required: true + LSP_REF: + description: 'LSP reference.' + required: true + CLIENT_REF: + description: 'Client reference.' + required: true + GO_VERSION: + description: 'Go version.' + required: true + CLN_VERSION: + description: 'Core Lightning version.' + required: true +runs: + using: 'composite' + steps: + - name: Download build artifacts + uses: actions/download-artifact@v3 + with: + name: build-artifacts + + - name: Set permissions + run: | + chmod 755 lspd + chmod 755 lspd_plugin + shell: bash + + - name: Cache LND client + uses: actions/cache@v3 + with: + path: ~/go_lnd_client/bin/lnd + key: go_lnd_client-${{ inputs.CLIENT_REF }}-${{ inputs.GO_VERSION }} + + - name: Cache LND LSP + uses: actions/cache@v3 + with: + path: ~/go_lnd_lsp/bin/lnd + key: go_lnd_lsp-${{ inputs.LSP_REF }}-${{ inputs.GO_VERSION }} + + - name: Cache Core Lightning + uses: actions/cache@v3 + with: + path: | + lightning_git/lightningd/lightning_hsmd + lightning_git/lightningd/lightning_gossipd + lightning_git/lightningd/lightning_openingd + lightning_git/lightningd/lightning_dualopend + lightning_git/lightningd/lightning_channeld + lightning_git/lightningd/lightning_closingd + lightning_git/lightningd/lightning_onchaind + lightning_git/lightningd/lightning_connectd + lightning_git/lightningd/lightning_websocketd + lightning_git/lightningd/lightningd + lightning_git/plugins/offers + lightning_git/plugins/topology + lightning_git/plugins/spenderp + lightning_git/plugins/test/run-route-overlong + lightning_git/plugins/test/run-funder_policy + lightning_git/plugins/pay + lightning_git/plugins/bkpr/test/run-bkpr_db + lightning_git/plugins/bkpr/test/run-recorder + lightning_git/plugins/funder + lightning_git/plugins/bookkeeper + lightning_git/plugins/txprepare + lightning_git/plugins/keysend + lightning_git/plugins/fetchinvoice + lightning_git/plugins/bcli + lightning_git/plugins/cln-grpc + lightning_git/plugins/commando + lightning_git/plugins/autoclean + lightning_git/plugins/chanbackup + lightning_git/plugins/sql + lightning_git/cli/lightning-cli + lightning_git/devtools/bolt11-cli + lightning_git/devtools/decodemsg + lightning_git/devtools/onion + lightning_git/devtools/dump-gossipstore + lightning_git/devtools/gossipwith + lightning_git/devtools/create-gossipstore + lightning_git/devtools/mkcommit + lightning_git/devtools/mkfunding + lightning_git/devtools/mkclose + lightning_git/devtools/mkgossip + key: core-lightning-${{ inputs.CLN_VERSION }} + + - name: Cache Bitcoin Core + uses: actions/cache@v3 + with: + path: | + ~/bitcoin-core-${{ inputs.bitcoin-version }}/bitcoin-${{ inputs.bitcoin-version }}/bin/bitcoind + ~/bitcoin-core-${{ inputs.bitcoin-version }}/bitcoin-${{ inputs.bitcoin-version }}/bin/bitcoin-cli + key: bitcoin-core-${{ inputs.bitcoin-version }} + + - name: Cache itest + id: cache-itest + uses: actions/cache@v3 + with: + path: | + ~/go + key: itest + + - name: Test LSPD + run: | + go get github.com/breez/lspd/itest + go test -timeout 45m -v \ + ./itest \ + -test.run \ + ${{ inputs.TESTRE }} \ + --bitcoindexec ~/bitcoin-core-${{ inputs.bitcoin-version }}/bitcoin-${{ inputs.bitcoin-version }}/bin/bitcoind \ + --bitcoincliexec ~/bitcoin-core-${{ inputs.bitcoin-version }}/bitcoin-${{ inputs.bitcoin-version }}/bin/bitcoin-cli \ + --lightningdexec ${{ github.workspace }}/lightning_git/lightningd/lightningd \ + --lndexec ~/go_lnd_lsp/bin/lnd \ + --lndmobileexec ~/go_lnd_client/bin/lnd \ + --clnpluginexec ${{ github.workspace }}/lspd_plugin \ + --lspdexec ${{ github.workspace }}/lspd \ + --lspdmigrationsdir ${{ github.workspace }}/postgresql/migrations \ + --preservelogs \ + --testdir /home/runner/test_state || echo "step_failed=true" >> $GITHUB_ENV + shell: bash + + - name: Process test state + uses: ./.github/actions/process-test-state + with: + artifact-name: ${{ inputs.artifact-name }} + test-state-path: /home/runner/test_state + + - name: Fail the workflow if the tests failed + run: | + if [[ "${{ env.step_failed }}" == "true" ]] + then + exit 1 + fi + shell: bash diff --git a/.github/workflows/integration_tests.yaml b/.github/workflows/integration_tests.yaml new file mode 100644 index 0000000..f531e3e --- /dev/null +++ b/.github/workflows/integration_tests.yaml @@ -0,0 +1,123 @@ +name: integration tests +on: [push] +env: + BITCOIN_VERSION: '22.0' + LSP_REF: 'breez-node-v0.16.4-beta' + CLIENT_REF: 'v0.16.4-beta-breez' + GO_VERSION: '^1.19' + CLN_VERSION: 'v23.05.1' +jobs: + + setup-bitcoin-core: + runs-on: ubuntu-22.04 + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Set up Bitcoin Core + if: steps.cache-bitcoin.outputs.cache-hit != 'true' + uses: ./.github/actions/setup-bitcoin + with: + bitcoin-version: ${{ env.BITCOIN_VERSION }} + + setup-lnd-lsp: + runs-on: ubuntu-22.04 + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Set up LND LSP + if: steps.cache-lnd-lsp.outputs.cache-hit != 'true' + uses: ./.github/actions/setup-lnd-lsp + with: + lsp-ref: ${{ env.LSP_REF }} + go-version: ${{ env.GO_VERSION }} + + setup-lnd-client: + runs-on: ubuntu-22.04 + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Set up LND client + if: steps.cache-lnd-client.outputs.cache-hit != 'true' + uses: ./.github/actions/setup-lnd-client + with: + client-ref: ${{ env.CLIENT_REF }} + go-version: ${{ env.GO_VERSION }} + + setup-cln: + runs-on: ubuntu-22.04 + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Set up Core Lightning + uses: ./.github/actions/setup-clightning + with: + checkout-version: ${{ env.CLN_VERSION }} + + build-lspd: + runs-on: ubuntu-22.04 + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Build LSPD and Upload Artifacts + uses: ./.github/actions/build-lspd + + setup-itest: + runs-on: ubuntu-22.04 + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Setup itest + uses: ./.github/actions/setup-itest + + + run-test: + runs-on: ubuntu-22.04 + needs: + - setup-itest + - setup-bitcoin-core + - setup-lnd-client + - setup-lnd-lsp + - setup-cln + - build-lspd + name: test ${{ matrix.implementation }} ${{ matrix.test }} + strategy: + max-parallel: 6 + matrix: + test: [ + testOpenZeroConfChannelOnReceive, + testOpenZeroConfSingleHtlc, + testZeroReserve, + testFailureBobOffline, + testNoBalance, + testRegularForward, + testProbing, + testInvalidCltv, + registerPaymentWithTag, + testOpenZeroConfUtxo, + testDynamicFeeFlow, + testOfflineNotificationPaymentRegistered, + testOfflineNotificationRegularForward, + testOfflineNotificationZeroConfChannel, + ] + implementation: [ + LND, + CLN + ] + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Run and Process Test State + uses: ./.github/actions/test-lspd + with: + TESTRE: "TestLspd/${{ matrix.implementation }}-lspd:_${{ matrix.test }}" + artifact-name: TestLspd-${{ matrix.implementation }}-lspd_${{ matrix.test }} + bitcoin-version: ${{ env.BITCOIN_VERSION }} + LSP_REF: ${{ env.LSP_REF }} + CLIENT_REF: ${{ env.CLIENT_REF }} + GO_VERSION: ${{ env.GO_VERSION }} + CLN_VERSION: ${{ env.CLN_VERSION }} diff --git a/.gitignore b/.gitignore index 7a25686..c9e8d7c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ +.vscode go.sum lspd +lspd_plugin From 5813a9a59c277162764b05b7422318e9112b7b9e Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Fri, 28 Jul 2023 17:07:40 +0200 Subject: [PATCH 209/214] run integration tests on pull request --- .github/workflows/integration_tests.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/integration_tests.yaml b/.github/workflows/integration_tests.yaml index f531e3e..5c5f667 100644 --- a/.github/workflows/integration_tests.yaml +++ b/.github/workflows/integration_tests.yaml @@ -1,5 +1,8 @@ name: integration tests -on: [push] +on: + push: + branches: [ master ] + pull_request: env: BITCOIN_VERSION: '22.0' LSP_REF: 'breez-node-v0.16.4-beta' From 349ef989ce21f52e9358a24d33f3bed2c4bf579e Mon Sep 17 00:00:00 2001 From: lndev Date: Thu, 13 Jul 2023 01:17:03 +0000 Subject: [PATCH 210/214] use correct version of glightning [issue #90] - Replace niftynei/glightning with elementsproject/glightning - Use latest version of breez/lntest --- cln/cln_client.go | 4 ++-- go.mod | 7 +++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/cln/cln_client.go b/cln/cln_client.go index 6bb6bbb..c80ffe0 100644 --- a/cln/cln_client.go +++ b/cln/cln_client.go @@ -12,7 +12,7 @@ import ( "github.com/breez/lspd/lightning" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" - "github.com/niftynei/glightning/glightning" + "github.com/elementsproject/glightning/glightning" "golang.org/x/exp/slices" ) @@ -122,7 +122,7 @@ func (c *ClnClient) OpenChannel(req *lightning.OpenChannelRequest) (*wire.OutPoi minConfs, glightning.NewMsat(0), minDepth, - glightning.NewSat(0), + glightning.NewMsat(0), ) if err != nil { diff --git a/go.mod b/go.mod index 14a06e6..1fe9f32 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.19 require ( github.com/aws/aws-sdk-go v1.34.0 - github.com/breez/lntest v0.0.23 + github.com/breez/lntest v0.0.25 github.com/btcsuite/btcd v0.23.5-0.20230228185050-38331963bddd github.com/btcsuite/btcd/btcec/v2 v2.3.2 github.com/btcsuite/btcd/chaincfg/chainhash v1.0.2 @@ -12,6 +12,7 @@ require ( github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 github.com/docker/docker v20.10.24+incompatible github.com/docker/go-connections v0.4.0 + github.com/elementsproject/glightning v0.0.0-20230525134205-ef34d849f564 github.com/golang/protobuf v1.5.2 github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 github.com/jackc/pgtype v1.8.1 @@ -19,7 +20,6 @@ require ( github.com/lightningnetwork/lightning-onion v1.2.1-0.20221202012345-ca23184850a1 github.com/lightningnetwork/lnd v0.16.2-beta github.com/lightningnetwork/lnd/tlv v1.1.0 - github.com/niftynei/glightning v0.8.2 github.com/stretchr/testify v1.8.1 go.starlark.net v0.0.0-20230612165344-9532f5667272 golang.org/x/exp v0.0.0-20230321023759-10a507213a29 @@ -189,5 +189,4 @@ require ( ) replace github.com/lightningnetwork/lnd v0.16.2-beta => github.com/breez/lnd v0.15.0-beta.rc6.0.20230501134702-cebcdf1b17fd - -replace github.com/niftynei/glightning v0.8.2 => github.com/breez/glightning v0.0.0-20221219103549-0e2a13b9b3ed +replace github.com/elementsproject/glightning => github.com/breez/glightning v0.0.1-breez From 114fb908ba31591ca9cb53a2510bffd14edd9f81 Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Mon, 31 Jul 2023 15:00:41 +0200 Subject: [PATCH 211/214] sort fee menu by min_msat AND proportional --- channel_opener_server.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/channel_opener_server.go b/channel_opener_server.go index 56d2bbb..fbb8a8d 100644 --- a/channel_opener_server.go +++ b/channel_opener_server.go @@ -105,6 +105,10 @@ func (s *channelOpenerServer) createOpeningParamsMenu( } sort.Slice(menu, func(i, j int) bool { + if menu[i].MinMsat == menu[j].MinMsat { + return menu[i].Proportional < menu[j].Proportional + } + return menu[i].MinMsat < menu[j].MinMsat }) return menu, nil From 7c12c5f7ec01af21ade65eecf18f4153d4cfcb80 Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Mon, 7 Aug 2023 11:41:34 +0200 Subject: [PATCH 212/214] use lntest 0.0.26 and update docs/ci --- .github/workflows/integration_tests.yaml | 4 ++-- README.md | 4 ++-- go.mod | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/integration_tests.yaml b/.github/workflows/integration_tests.yaml index 5c5f667..66677eb 100644 --- a/.github/workflows/integration_tests.yaml +++ b/.github/workflows/integration_tests.yaml @@ -4,9 +4,9 @@ on: branches: [ master ] pull_request: env: - BITCOIN_VERSION: '22.0' + BITCOIN_VERSION: '25.0' LSP_REF: 'breez-node-v0.16.4-beta' - CLIENT_REF: 'v0.16.4-beta-breez' + CLIENT_REF: 'v0.16.4-breez-2' GO_VERSION: '^1.19' CLN_VERSION: 'v23.05.1' jobs: diff --git a/README.md b/README.md index b91f589..bd6a14c 100644 --- a/README.md +++ b/README.md @@ -57,8 +57,8 @@ In order to run the integration tests, you need: - Docker running - python3 installed - A development build of lightningd v23.05.1 -- lnd v0.16.2 lsp version https://github.com/breez/lnd/commit/cebcdf1b17fdedf7d69207d98c31cf8c3b257531 -- lnd v0.16.2 breez client version https://github.com/breez/lnd/commit/9d744cd396af707d77473d58c97947b8e0a25d08 +- lnd v0.16.4 lsp version https://github.com/breez/lnd/commit/cebcdf1b17fdedf7d69207d98c31cf8c3b257531 +- lnd v0.16.4 breez client version https://github.com/breez/lnd/commit/3c0854adcfc924a6d759a6ee4640c41266b9f8b4 - bitcoind (tested with v23.0) - bitcoin-cli (tested with v23.0) - build of lspd (go build .) diff --git a/go.mod b/go.mod index 1fe9f32..112807a 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.19 require ( github.com/aws/aws-sdk-go v1.34.0 - github.com/breez/lntest v0.0.25 + github.com/breez/lntest v0.0.26 github.com/btcsuite/btcd v0.23.5-0.20230228185050-38331963bddd github.com/btcsuite/btcd/btcec/v2 v2.3.2 github.com/btcsuite/btcd/chaincfg/chainhash v1.0.2 From 327d9d37a6f243ef6f364cde0fc9d5f5417dca65 Mon Sep 17 00:00:00 2001 From: Jesse de Wit Date: Mon, 7 Aug 2023 11:59:03 +0200 Subject: [PATCH 213/214] use bitcoincore.org for CI --- .github/actions/setup-bitcoin/action.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/setup-bitcoin/action.yaml b/.github/actions/setup-bitcoin/action.yaml index f07d87a..34ad29f 100644 --- a/.github/actions/setup-bitcoin/action.yaml +++ b/.github/actions/setup-bitcoin/action.yaml @@ -28,7 +28,7 @@ runs: run: | mkdir -p ~/bitcoin-core-${{ inputs.bitcoin-version }} cd ~/bitcoin-core-${{ inputs.bitcoin-version }} - axel https://bitcoin.org/bin/bitcoin-core-${{ inputs.bitcoin-version }}/bitcoin-${{ inputs.bitcoin-version }}-x86_64-linux-gnu.tar.gz + axel https://bitcoincore.org/bin/bitcoin-core-${{ inputs.bitcoin-version }}/bitcoin-${{ inputs.bitcoin-version }}-x86_64-linux-gnu.tar.gz tar -xzf bitcoin-${{ inputs.bitcoin-version }}-x86_64-linux-gnu.tar.gz rm bitcoin-${{ inputs.bitcoin-version }}-x86_64-linux-gnu.tar.gz sudo cp ~/bitcoin-core-${{ inputs.bitcoin-version }}/bitcoin-${{ inputs.bitcoin-version }}/bin/bitcoind /usr/bin/ From 72c2b4742e2665153c245c2fadd20a88cca5243e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Aug 2023 10:14:46 +0000 Subject: [PATCH 214/214] Bump github.com/docker/distribution Bumps [github.com/docker/distribution](https://github.com/docker/distribution) from 2.8.1+incompatible to 2.8.2+incompatible. - [Release notes](https://github.com/docker/distribution/releases) - [Commits](https://github.com/docker/distribution/compare/v2.8.1...v2.8.2) --- updated-dependencies: - dependency-name: github.com/docker/distribution dependency-type: indirect ... Signed-off-by: dependabot[bot] --- go.mod | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 112807a..4525e3e 100644 --- a/go.mod +++ b/go.mod @@ -30,7 +30,7 @@ require ( require ( github.com/Microsoft/go-winio v0.5.2 // indirect github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344 // indirect - github.com/docker/distribution v2.8.1+incompatible // indirect + github.com/docker/distribution v2.8.2+incompatible // indirect github.com/docker/go-units v0.5.0 // indirect github.com/ethereum/go-ethereum v1.10.17 // indirect github.com/golang-jwt/jwt/v4 v4.4.2 // indirect @@ -189,4 +189,5 @@ require ( ) replace github.com/lightningnetwork/lnd v0.16.2-beta => github.com/breez/lnd v0.15.0-beta.rc6.0.20230501134702-cebcdf1b17fd + replace github.com/elementsproject/glightning => github.com/breez/glightning v0.0.1-breez