From a764ca6de9e0849409fd4ef40dcfb120f53b30ba Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Tue, 17 Jan 2023 15:40:08 +0100 Subject: [PATCH 01/15] start work on eclair support --- lib/service/config.go | 8 +++ lnd/eclair.go | 116 ++++++++++++++++++++++++++++++++++++++++++ main.go | 29 +++++++---- 3 files changed, 144 insertions(+), 9 deletions(-) create mode 100644 lnd/eclair.go diff --git a/lib/service/config.go b/lib/service/config.go index cd419023..b699887a 100644 --- a/lib/service/config.go +++ b/lib/service/config.go @@ -5,6 +5,11 @@ import ( "strings" ) +const ( + LND_CLIENT_TYPE = "lnd" + ECLAIR_CLIENT_TYPE = "eclair" +) + type Config struct { DatabaseUri string `envconfig:"DATABASE_URI" required:"true"` SentryDSN string `envconfig:"SENTRY_DSN"` @@ -14,11 +19,14 @@ type Config struct { AdminToken string `envconfig:"ADMIN_TOKEN"` JWTRefreshTokenExpiry int `envconfig:"JWT_REFRESH_EXPIRY" default:"604800"` // in seconds, default 7 days JWTAccessTokenExpiry int `envconfig:"JWT_ACCESS_EXPIRY" default:"172800"` // in seconds, default 2 days + LNClientType string `envconfig:"LN_CLIENT_TYPE" default:"lnd"` //lnd, eclair? LNDAddress string `envconfig:"LND_ADDRESS" required:"true"` LNDMacaroonFile string `envconfig:"LND_MACAROON_FILE"` LNDCertFile string `envconfig:"LND_CERT_FILE"` LNDMacaroonHex string `envconfig:"LND_MACAROON_HEX"` LNDCertHex string `envconfig:"LND_CERT_HEX"` + EclairHost string `envconfig:"ECLAIR_HOST"` + EclairPassword string `envconfig:"ECLAIR_PASSWORD"` CustomName string `envconfig:"CUSTOM_NAME"` Host string `envconfig:"HOST" default:"localhost:3000"` Port int `envconfig:"PORT" default:"3000"` diff --git a/lnd/eclair.go b/lnd/eclair.go new file mode 100644 index 00000000..908096d2 --- /dev/null +++ b/lnd/eclair.go @@ -0,0 +1,116 @@ +package lnd + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + + "github.com/lightningnetwork/lnd/lnrpc" + "github.com/lightningnetwork/lnd/lnrpc/routerrpc" + "google.golang.org/grpc" +) + +type EclairClient struct { + host string + password string +} + +func NewEclairClient(host, password string) *EclairClient { + return &EclairClient{ + host: host, + password: password, + } +} + +func (eclair *EclairClient) ListChannels(ctx context.Context, req *lnrpc.ListChannelsRequest, options ...grpc.CallOption) (*lnrpc.ListChannelsResponse, error) { + panic("not implemented") // TODO: Implement +} + +func (eclair *EclairClient) SendPaymentSync(ctx context.Context, req *lnrpc.SendRequest, options ...grpc.CallOption) (*lnrpc.SendResponse, error) { + panic("not implemented") // TODO: Implement +} + +func (eclair *EclairClient) AddInvoice(ctx context.Context, req *lnrpc.Invoice, options ...grpc.CallOption) (*lnrpc.AddInvoiceResponse, error) { + panic("not implemented") // TODO: Implement +} + +func (eclair *EclairClient) SubscribeInvoices(ctx context.Context, req *lnrpc.InvoiceSubscription, options ...grpc.CallOption) (SubscribeInvoicesWrapper, error) { + panic("not implemented") // TODO: Implement +} + +func (eclair *EclairClient) SubscribePayment(ctx context.Context, req *routerrpc.TrackPaymentRequest, options ...grpc.CallOption) (SubscribePaymentWrapper, error) { + panic("not implemented") // TODO: Implement +} + +func (eclair *EclairClient) GetInfo(ctx context.Context, req *lnrpc.GetInfoRequest, options ...grpc.CallOption) (*lnrpc.GetInfoResponse, error) { + httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, fmt.Sprintf("%s/getinfo", eclair.host), nil) + httpReq.SetBasicAuth("", eclair.password) + resp, err := http.DefaultClient.Do(httpReq) + if err != nil { + return nil, err + } + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("Got a bad http response status code from Eclair %d", resp.StatusCode) + } + info := InfoResponse{} + json.NewDecoder(resp.Body).Decode(&info) + return &lnrpc.GetInfoResponse{ + Version: info.Version, + CommitHash: "", + IdentityPubkey: info.NodeID, + Alias: info.Alias, + Color: info.Color, + NumPendingChannels: 0, + NumActiveChannels: 0, + NumInactiveChannels: 0, + NumPeers: 0, + BlockHeight: uint32(info.BlockHeight), + BlockHash: "", + BestHeaderTimestamp: 0, + SyncedToChain: false, + SyncedToGraph: false, + Testnet: false, + Chains: []*lnrpc.Chain{{ + Chain: "bitcoin", + Network: info.Network, + }}, + Uris: []string{}, + Features: map[uint32]*lnrpc.Feature{}, + RequireHtlcInterceptor: false, + }, nil +} + +func (eclair *EclairClient) DecodeBolt11(ctx context.Context, bolt11 string, options ...grpc.CallOption) (*lnrpc.PayReq, error) { + panic("not implemented") // TODO: Implement +} + +type InfoResponse struct { + Version string `json:"version"` + NodeID string `json:"nodeId"` + Alias string `json:"alias"` + Color string `json:"color"` + Features struct { + Activated struct { + OptionOnionMessages string `json:"option_onion_messages"` + GossipQueriesEx string `json:"gossip_queries_ex"` + OptionPaymentMetadata string `json:"option_payment_metadata"` + OptionDataLossProtect string `json:"option_data_loss_protect"` + VarOnionOptin string `json:"var_onion_optin"` + OptionStaticRemotekey string `json:"option_static_remotekey"` + OptionSupportLargeChannel string `json:"option_support_large_channel"` + OptionAnchorsZeroFeeHtlcTx string `json:"option_anchors_zero_fee_htlc_tx"` + PaymentSecret string `json:"payment_secret"` + OptionShutdownAnysegwit string `json:"option_shutdown_anysegwit"` + OptionChannelType string `json:"option_channel_type"` + BasicMpp string `json:"basic_mpp"` + GossipQueries string `json:"gossip_queries"` + } `json:"activated"` + Unknown []interface{} `json:"unknown"` + } `json:"features"` + ChainHash string `json:"chainHash"` + Network string `json:"network"` + BlockHeight int `json:"blockHeight"` + PublicAddresses []string `json:"publicAddresses"` + InstanceID string `json:"instanceId"` +} diff --git a/main.go b/main.go index 5bbc7f4c..0704192f 100644 --- a/main.go +++ b/main.go @@ -128,16 +128,10 @@ func main() { e.Use(sentryecho.New(sentryecho.Options{})) } - // Init new LND client - lndClient, err := lnd.NewLNDclient(lnd.LNDoptions{ - Address: c.LNDAddress, - MacaroonFile: c.LNDMacaroonFile, - MacaroonHex: c.LNDMacaroonHex, - CertFile: c.LNDCertFile, - CertHex: c.LNDCertHex, - }) + // Init new LN client + lndClient, err := createLnClient(c) if err != nil { - e.Logger.Fatalf("Error initializing the LND connection: %v", err) + e.Logger.Fatalf("Error initializing LN client %s", err.Error()) } getInfo, err := lndClient.GetInfo(ctx, &lnrpc.GetInfoRequest{}) if err != nil { @@ -244,6 +238,23 @@ func createRateLimitMiddleware(seconds int, burst int) echo.MiddlewareFunc { return middleware.RateLimiter(middleware.NewRateLimiterMemoryStoreWithConfig(config)) } +func createLnClient(c *service.Config) (lnd.LightningClientWrapper, error) { + switch c.LNClientType { + case service.LND_CLIENT_TYPE: + return lnd.NewLNDclient(lnd.LNDoptions{ + Address: c.LNDAddress, + MacaroonFile: c.LNDMacaroonFile, + MacaroonHex: c.LNDMacaroonHex, + CertFile: c.LNDCertFile, + CertHex: c.LNDCertHex, + }) + case service.ECLAIR_CLIENT_TYPE: + return lnd.NewEclairClient(c.EclairHost, c.EclairPassword), nil + default: + return nil, fmt.Errorf("Client type not recognized: %s", c.LNClientType) + } +} + func createCacheClient() *cache.Client { memcached, err := memory.NewAdapter( memory.AdapterWithAlgorithm(memory.LRU), From 5e56d07f235c9beb458330ded7cf8a1fe8f7d8bf Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Tue, 17 Jan 2023 15:43:22 +0100 Subject: [PATCH 02/15] start work on eclair support --- main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.go b/main.go index 0704192f..b0ff36e8 100644 --- a/main.go +++ b/main.go @@ -137,7 +137,7 @@ func main() { if err != nil { e.Logger.Fatalf("Error getting node info: %v", err) } - logger.Infof("Connected to LND: %s - %s", getInfo.Alias, getInfo.IdentityPubkey) + logger.Infof("Connected to %s: %s - %s", c.LNClientType, getInfo.Alias, getInfo.IdentityPubkey) svc := &service.LndhubService{ Config: c, From d443cd9864a85ab69b1066d853902cc0e7d5a91a Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Tue, 17 Jan 2023 17:14:20 +0100 Subject: [PATCH 03/15] some more work on eclair support --- lnd/eclair.go | 141 +++++++++++++++++++--------- lnd/eclair_models.go | 214 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 311 insertions(+), 44 deletions(-) create mode 100644 lnd/eclair_models.go diff --git a/lnd/eclair.go b/lnd/eclair.go index 908096d2..8ae7aecf 100644 --- a/lnd/eclair.go +++ b/lnd/eclair.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "fmt" + "io" "net/http" "github.com/lightningnetwork/lnd/lnrpc" @@ -16,6 +17,28 @@ type EclairClient struct { password string } +type EclairInvoicesSubscriber struct { + ctx context.Context +} + +func (eis *EclairInvoicesSubscriber) Recv() (*lnrpc.Invoice, error) { + //placeholder + //block indefinitely + <-eis.ctx.Done() + return nil, fmt.Errorf("context canceled") +} + +type EclairPaymentsTracker struct { + ctx context.Context +} + +func (ept *EclairPaymentsTracker) Recv() (*lnrpc.Payment, error) { + //placeholder + //block indefinitely + <-ept.ctx.Done() + return nil, fmt.Errorf("context canceled") +} + func NewEclairClient(host, password string) *EclairClient { return &EclairClient{ host: host, @@ -24,7 +47,52 @@ func NewEclairClient(host, password string) *EclairClient { } func (eclair *EclairClient) ListChannels(ctx context.Context, req *lnrpc.ListChannelsRequest, options ...grpc.CallOption) (*lnrpc.ListChannelsResponse, error) { - panic("not implemented") // TODO: Implement + channels := []EclairChannel{} + err := eclair.Request(ctx, http.MethodPost, "/channels", nil, &channels) + if err != nil { + return nil, err + } + convertedChannels := []*lnrpc.Channel{} + for _, ch := range channels { + convertedChannels = append(convertedChannels, &lnrpc.Channel{ + Active: ch.State == "NORMAL", + RemotePubkey: ch.NodeID, + ChannelPoint: "", + ChanId: 0, + Capacity: int64(ch.Data.Commitments.LocalCommit.Spec.ToLocal)/1000 + int64(ch.Data.Commitments.LocalCommit.Spec.ToRemote)/1000, + LocalBalance: int64(ch.Data.Commitments.LocalCommit.Spec.ToLocal) / 1000, + RemoteBalance: int64(ch.Data.Commitments.LocalCommit.Spec.ToRemote) / 1000, + CommitFee: 0, + CommitWeight: 0, + FeePerKw: 0, + UnsettledBalance: 0, + TotalSatoshisSent: 0, + TotalSatoshisReceived: 0, + NumUpdates: 0, + PendingHtlcs: []*lnrpc.HTLC{}, + CsvDelay: 0, + Private: false, + Initiator: false, + ChanStatusFlags: "", + LocalChanReserveSat: 0, + RemoteChanReserveSat: 0, + StaticRemoteKey: false, + CommitmentType: 0, + Lifetime: 0, + Uptime: 0, + CloseAddress: "", + PushAmountSat: 0, + ThawHeight: 0, + LocalConstraints: &lnrpc.ChannelConstraints{}, + RemoteConstraints: &lnrpc.ChannelConstraints{}, + AliasScids: []uint64{}, + ZeroConf: false, + ZeroConfConfirmedScid: 0, + }) + } + return &lnrpc.ListChannelsResponse{ + Channels: convertedChannels, + }, nil } func (eclair *EclairClient) SendPaymentSync(ctx context.Context, req *lnrpc.SendRequest, options ...grpc.CallOption) (*lnrpc.SendResponse, error) { @@ -36,25 +104,27 @@ func (eclair *EclairClient) AddInvoice(ctx context.Context, req *lnrpc.Invoice, } func (eclair *EclairClient) SubscribeInvoices(ctx context.Context, req *lnrpc.InvoiceSubscription, options ...grpc.CallOption) (SubscribeInvoicesWrapper, error) { - panic("not implemented") // TODO: Implement + return &EclairInvoicesSubscriber{ + ctx: ctx, + }, nil } func (eclair *EclairClient) SubscribePayment(ctx context.Context, req *routerrpc.TrackPaymentRequest, options ...grpc.CallOption) (SubscribePaymentWrapper, error) { - panic("not implemented") // TODO: Implement + return &EclairPaymentsTracker{ + ctx: ctx, + }, nil } func (eclair *EclairClient) GetInfo(ctx context.Context, req *lnrpc.GetInfoRequest, options ...grpc.CallOption) (*lnrpc.GetInfoResponse, error) { - httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, fmt.Sprintf("%s/getinfo", eclair.host), nil) - httpReq.SetBasicAuth("", eclair.password) - resp, err := http.DefaultClient.Do(httpReq) + info := EclairInfoResponse{} + err := eclair.Request(ctx, http.MethodPost, "/getinfo", nil, &info) if err != nil { return nil, err } - if resp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("Got a bad http response status code from Eclair %d", resp.StatusCode) + addresses := []string{} + for _, addr := range info.PublicAddresses { + addresses = append(addresses, fmt.Sprintf("%s@%s", info.NodeID, addr)) } - info := InfoResponse{} - json.NewDecoder(resp.Body).Decode(&info) return &lnrpc.GetInfoResponse{ Version: info.Version, CommitHash: "", @@ -68,49 +138,32 @@ func (eclair *EclairClient) GetInfo(ctx context.Context, req *lnrpc.GetInfoReque BlockHeight: uint32(info.BlockHeight), BlockHash: "", BestHeaderTimestamp: 0, - SyncedToChain: false, - SyncedToGraph: false, - Testnet: false, + SyncedToChain: true, + SyncedToGraph: true, + Testnet: info.Network == "testnet", Chains: []*lnrpc.Chain{{ Chain: "bitcoin", Network: info.Network, }}, - Uris: []string{}, + Uris: addresses, Features: map[uint32]*lnrpc.Feature{}, RequireHtlcInterceptor: false, }, nil } -func (eclair *EclairClient) DecodeBolt11(ctx context.Context, bolt11 string, options ...grpc.CallOption) (*lnrpc.PayReq, error) { - panic("not implemented") // TODO: Implement +func (eclair *EclairClient) Request(ctx context.Context, method, endpoint string, body io.Reader, response interface{}) error { + httpReq, err := http.NewRequestWithContext(ctx, method, fmt.Sprintf("%s%s", eclair.host, endpoint), body) + httpReq.SetBasicAuth("", eclair.password) + resp, err := http.DefaultClient.Do(httpReq) + if err != nil { + return err + } + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("Got a bad http response status code from Eclair %d for request %s", resp.StatusCode, httpReq.URL) + } + return json.NewDecoder(resp.Body).Decode(response) } -type InfoResponse struct { - Version string `json:"version"` - NodeID string `json:"nodeId"` - Alias string `json:"alias"` - Color string `json:"color"` - Features struct { - Activated struct { - OptionOnionMessages string `json:"option_onion_messages"` - GossipQueriesEx string `json:"gossip_queries_ex"` - OptionPaymentMetadata string `json:"option_payment_metadata"` - OptionDataLossProtect string `json:"option_data_loss_protect"` - VarOnionOptin string `json:"var_onion_optin"` - OptionStaticRemotekey string `json:"option_static_remotekey"` - OptionSupportLargeChannel string `json:"option_support_large_channel"` - OptionAnchorsZeroFeeHtlcTx string `json:"option_anchors_zero_fee_htlc_tx"` - PaymentSecret string `json:"payment_secret"` - OptionShutdownAnysegwit string `json:"option_shutdown_anysegwit"` - OptionChannelType string `json:"option_channel_type"` - BasicMpp string `json:"basic_mpp"` - GossipQueries string `json:"gossip_queries"` - } `json:"activated"` - Unknown []interface{} `json:"unknown"` - } `json:"features"` - ChainHash string `json:"chainHash"` - Network string `json:"network"` - BlockHeight int `json:"blockHeight"` - PublicAddresses []string `json:"publicAddresses"` - InstanceID string `json:"instanceId"` +func (eclair *EclairClient) DecodeBolt11(ctx context.Context, bolt11 string, options ...grpc.CallOption) (*lnrpc.PayReq, error) { + panic("not implemented") // TODO: Implement } diff --git a/lnd/eclair_models.go b/lnd/eclair_models.go new file mode 100644 index 00000000..dbc531a3 --- /dev/null +++ b/lnd/eclair_models.go @@ -0,0 +1,214 @@ +package lnd + +import "time" + +type EclairInfoResponse struct { + Version string `json:"version"` + NodeID string `json:"nodeId"` + Alias string `json:"alias"` + Color string `json:"color"` + Features struct { + Activated struct { + OptionOnionMessages string `json:"option_onion_messages"` + GossipQueriesEx string `json:"gossip_queries_ex"` + OptionPaymentMetadata string `json:"option_payment_metadata"` + OptionDataLossProtect string `json:"option_data_loss_protect"` + VarOnionOptin string `json:"var_onion_optin"` + OptionStaticRemotekey string `json:"option_static_remotekey"` + OptionSupportLargeChannel string `json:"option_support_large_channel"` + OptionAnchorsZeroFeeHtlcTx string `json:"option_anchors_zero_fee_htlc_tx"` + PaymentSecret string `json:"payment_secret"` + OptionShutdownAnysegwit string `json:"option_shutdown_anysegwit"` + OptionChannelType string `json:"option_channel_type"` + BasicMpp string `json:"basic_mpp"` + GossipQueries string `json:"gossip_queries"` + } `json:"activated"` + Unknown []interface{} `json:"unknown"` + } `json:"features"` + ChainHash string `json:"chainHash"` + Network string `json:"network"` + BlockHeight int `json:"blockHeight"` + PublicAddresses []string `json:"publicAddresses"` + InstanceID string `json:"instanceId"` +} + +type EclairChannel struct { + NodeID string `json:"nodeId"` + ChannelID string `json:"channelId"` + State string `json:"state"` + Data struct { + Type string `json:"type"` + Commitments struct { + ChannelID string `json:"channelId"` + ChannelConfig []string `json:"channelConfig"` + ChannelFeatures []string `json:"channelFeatures"` + LocalParams struct { + NodeID string `json:"nodeId"` + FundingKeyPath struct { + Path []interface{} `json:"path"` + } `json:"fundingKeyPath"` + DustLimit int `json:"dustLimit"` + MaxHtlcValueInFlightMsat float64 `json:"maxHtlcValueInFlightMsat"` + RequestedChannelReserveOpt int `json:"requestedChannelReserve_opt"` + HtlcMinimum int `json:"htlcMinimum"` + ToSelfDelay int `json:"toSelfDelay"` + MaxAcceptedHtlcs int `json:"maxAcceptedHtlcs"` + IsInitiator bool `json:"isInitiator"` + DefaultFinalScriptPubKey string `json:"defaultFinalScriptPubKey"` + InitFeatures struct { + Activated struct { + OptionOnionMessages string `json:"option_onion_messages"` + GossipQueriesEx string `json:"gossip_queries_ex"` + OptionDataLossProtect string `json:"option_data_loss_protect"` + VarOnionOptin string `json:"var_onion_optin"` + OptionStaticRemotekey string `json:"option_static_remotekey"` + OptionSupportLargeChannel string `json:"option_support_large_channel"` + OptionAnchorsZeroFeeHtlcTx string `json:"option_anchors_zero_fee_htlc_tx"` + PaymentSecret string `json:"payment_secret"` + OptionShutdownAnysegwit string `json:"option_shutdown_anysegwit"` + OptionChannelType string `json:"option_channel_type"` + BasicMpp string `json:"basic_mpp"` + GossipQueries string `json:"gossip_queries"` + } `json:"activated"` + Unknown []interface{} `json:"unknown"` + } `json:"initFeatures"` + } `json:"localParams"` + RemoteParams struct { + NodeID string `json:"nodeId"` + DustLimit int `json:"dustLimit"` + MaxHtlcValueInFlightMsat float64 `json:"maxHtlcValueInFlightMsat"` + RequestedChannelReserveOpt int `json:"requestedChannelReserve_opt"` + HtlcMinimum int `json:"htlcMinimum"` + ToSelfDelay int `json:"toSelfDelay"` + MaxAcceptedHtlcs int `json:"maxAcceptedHtlcs"` + FundingPubKey string `json:"fundingPubKey"` + RevocationBasepoint string `json:"revocationBasepoint"` + PaymentBasepoint string `json:"paymentBasepoint"` + DelayedPaymentBasepoint string `json:"delayedPaymentBasepoint"` + HtlcBasepoint string `json:"htlcBasepoint"` + InitFeatures struct { + Activated struct { + OptionOnionMessages string `json:"option_onion_messages"` + GossipQueriesEx string `json:"gossip_queries_ex"` + OptionDataLossProtect string `json:"option_data_loss_protect"` + VarOnionOptin string `json:"var_onion_optin"` + OptionStaticRemotekey string `json:"option_static_remotekey"` + OptionSupportLargeChannel string `json:"option_support_large_channel"` + OptionAnchorsZeroFeeHtlcTx string `json:"option_anchors_zero_fee_htlc_tx"` + PaymentSecret string `json:"payment_secret"` + OptionShutdownAnysegwit string `json:"option_shutdown_anysegwit"` + OptionChannelType string `json:"option_channel_type"` + BasicMpp string `json:"basic_mpp"` + GossipQueries string `json:"gossip_queries"` + } `json:"activated"` + Unknown []interface{} `json:"unknown"` + } `json:"initFeatures"` + } `json:"remoteParams"` + ChannelFlags struct { + AnnounceChannel bool `json:"announceChannel"` + } `json:"channelFlags"` + LocalCommit struct { + Index int `json:"index"` + Spec struct { + Htlcs []interface{} `json:"htlcs"` + CommitTxFeerate int `json:"commitTxFeerate"` + ToLocal int `json:"toLocal"` + ToRemote int `json:"toRemote"` + } `json:"spec"` + CommitTxAndRemoteSig struct { + CommitTx struct { + Txid string `json:"txid"` + Tx string `json:"tx"` + } `json:"commitTx"` + RemoteSig string `json:"remoteSig"` + } `json:"commitTxAndRemoteSig"` + HtlcTxsAndRemoteSigs []interface{} `json:"htlcTxsAndRemoteSigs"` + } `json:"localCommit"` + RemoteCommit struct { + Index int `json:"index"` + Spec struct { + Htlcs []interface{} `json:"htlcs"` + CommitTxFeerate int `json:"commitTxFeerate"` + ToLocal int `json:"toLocal"` + ToRemote int `json:"toRemote"` + } `json:"spec"` + Txid string `json:"txid"` + RemotePerCommitmentPoint string `json:"remotePerCommitmentPoint"` + } `json:"remoteCommit"` + LocalChanges struct { + Proposed []interface{} `json:"proposed"` + Signed []interface{} `json:"signed"` + Acked []interface{} `json:"acked"` + } `json:"localChanges"` + RemoteChanges struct { + Proposed []interface{} `json:"proposed"` + Acked []interface{} `json:"acked"` + Signed []interface{} `json:"signed"` + } `json:"remoteChanges"` + LocalNextHtlcID int `json:"localNextHtlcId"` + RemoteNextHtlcID int `json:"remoteNextHtlcId"` + OriginChannels struct { + } `json:"originChannels"` + RemoteNextCommitInfo string `json:"remoteNextCommitInfo"` + CommitInput struct { + OutPoint string `json:"outPoint"` + AmountSatoshis int `json:"amountSatoshis"` + } `json:"commitInput"` + RemotePerCommitmentSecrets interface{} `json:"remotePerCommitmentSecrets"` + } `json:"commitments"` + ShortIds struct { + Real struct { + Status string `json:"status"` + RealScid string `json:"realScid"` + } `json:"real"` + LocalAlias string `json:"localAlias"` + RemoteAlias string `json:"remoteAlias"` + } `json:"shortIds"` + ChannelAnnouncement struct { + NodeSignature1 string `json:"nodeSignature1"` + NodeSignature2 string `json:"nodeSignature2"` + BitcoinSignature1 string `json:"bitcoinSignature1"` + BitcoinSignature2 string `json:"bitcoinSignature2"` + Features struct { + Activated struct { + } `json:"activated"` + Unknown []interface{} `json:"unknown"` + } `json:"features"` + ChainHash string `json:"chainHash"` + ShortChannelID string `json:"shortChannelId"` + NodeID1 string `json:"nodeId1"` + NodeID2 string `json:"nodeId2"` + BitcoinKey1 string `json:"bitcoinKey1"` + BitcoinKey2 string `json:"bitcoinKey2"` + TlvStream struct { + Records []interface{} `json:"records"` + Unknown []interface{} `json:"unknown"` + } `json:"tlvStream"` + } `json:"channelAnnouncement"` + ChannelUpdate struct { + Signature string `json:"signature"` + ChainHash string `json:"chainHash"` + ShortChannelID string `json:"shortChannelId"` + Timestamp struct { + Iso time.Time `json:"iso"` + Unix int `json:"unix"` + } `json:"timestamp"` + MessageFlags struct { + DontForward bool `json:"dontForward"` + } `json:"messageFlags"` + ChannelFlags struct { + IsEnabled bool `json:"isEnabled"` + IsNode1 bool `json:"isNode1"` + } `json:"channelFlags"` + CltvExpiryDelta int `json:"cltvExpiryDelta"` + HtlcMinimumMsat int `json:"htlcMinimumMsat"` + FeeBaseMsat int `json:"feeBaseMsat"` + FeeProportionalMillionths int `json:"feeProportionalMillionths"` + HtlcMaximumMsat int `json:"htlcMaximumMsat"` + TlvStream struct { + Records []interface{} `json:"records"` + Unknown []interface{} `json:"unknown"` + } `json:"tlvStream"` + } `json:"channelUpdate"` + } `json:"data"` +} From 8c0c950c342281441aa89a770524baacb998fce2 Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Wed, 18 Jan 2023 13:12:06 +0100 Subject: [PATCH 04/15] eclair: add invoice support --- lnd/eclair.go | 44 +++++++++++++++++++++++++++++++++++++------- lnd/eclair_models.go | 23 +++++++++++++++++++++++ 2 files changed, 60 insertions(+), 7 deletions(-) diff --git a/lnd/eclair.go b/lnd/eclair.go index 8ae7aecf..1ce4dcbd 100644 --- a/lnd/eclair.go +++ b/lnd/eclair.go @@ -2,10 +2,13 @@ package lnd import ( "context" + "encoding/hex" "encoding/json" "fmt" - "io" "net/http" + "net/url" + "strconv" + "strings" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/routerrpc" @@ -48,7 +51,7 @@ func NewEclairClient(host, password string) *EclairClient { func (eclair *EclairClient) ListChannels(ctx context.Context, req *lnrpc.ListChannelsRequest, options ...grpc.CallOption) (*lnrpc.ListChannelsResponse, error) { channels := []EclairChannel{} - err := eclair.Request(ctx, http.MethodPost, "/channels", nil, &channels) + err := eclair.Request(ctx, http.MethodPost, "/channels", "", nil, &channels) if err != nil { return nil, err } @@ -100,7 +103,31 @@ func (eclair *EclairClient) SendPaymentSync(ctx context.Context, req *lnrpc.Send } func (eclair *EclairClient) AddInvoice(ctx context.Context, req *lnrpc.Invoice, options ...grpc.CallOption) (*lnrpc.AddInvoiceResponse, error) { - panic("not implemented") // TODO: Implement + payload := url.Values{} + ///Description: req.Memo, + ///DescriptionHash: string(req.DescriptionHash), + ///AmountMsat: req.Value, + ///ExpireIn: req.Expiry, + ///paymentPreimage: hex.EncodeToString(req.RPreimage), + if req.Memo != "" { + payload.Add("description", req.Memo) + } + if len(req.DescriptionHash) != 0 { + payload.Add("descriptionHash", string(req.DescriptionHash)) + } + payload.Add("amountMsat", strconv.Itoa(int(req.Value*1000))) + payload.Add("paymentPreimage", hex.EncodeToString(req.RPreimage)) + payload.Add("expireIn", strconv.Itoa(int(req.Expiry))) + invoice := &EclairInvoice{} + err := eclair.Request(ctx, http.MethodPost, "/createinvoice", "application/x-www-form-urlencoded", payload, invoice) + if err != nil { + return nil, err + } + return &lnrpc.AddInvoiceResponse{ + RHash: []byte(invoice.PaymentHash), + PaymentRequest: invoice.Serialized, + AddIndex: uint64(invoice.Timestamp), + }, nil } func (eclair *EclairClient) SubscribeInvoices(ctx context.Context, req *lnrpc.InvoiceSubscription, options ...grpc.CallOption) (SubscribeInvoicesWrapper, error) { @@ -117,7 +144,7 @@ func (eclair *EclairClient) SubscribePayment(ctx context.Context, req *routerrpc func (eclair *EclairClient) GetInfo(ctx context.Context, req *lnrpc.GetInfoRequest, options ...grpc.CallOption) (*lnrpc.GetInfoResponse, error) { info := EclairInfoResponse{} - err := eclair.Request(ctx, http.MethodPost, "/getinfo", nil, &info) + err := eclair.Request(ctx, http.MethodPost, "/getinfo", "", nil, &info) if err != nil { return nil, err } @@ -151,15 +178,18 @@ func (eclair *EclairClient) GetInfo(ctx context.Context, req *lnrpc.GetInfoReque }, nil } -func (eclair *EclairClient) Request(ctx context.Context, method, endpoint string, body io.Reader, response interface{}) error { - httpReq, err := http.NewRequestWithContext(ctx, method, fmt.Sprintf("%s%s", eclair.host, endpoint), body) +func (eclair *EclairClient) Request(ctx context.Context, method, endpoint, contentType string, body url.Values, response interface{}) error { + httpReq, err := http.NewRequestWithContext(ctx, method, fmt.Sprintf("%s%s", eclair.host, endpoint), strings.NewReader(body.Encode())) + httpReq.Header.Set("Content-type", contentType) httpReq.SetBasicAuth("", eclair.password) resp, err := http.DefaultClient.Do(httpReq) if err != nil { return err } if resp.StatusCode != http.StatusOK { - return fmt.Errorf("Got a bad http response status code from Eclair %d for request %s", resp.StatusCode, httpReq.URL) + response := map[string]interface{}{} + json.NewDecoder(resp.Body).Decode(&response) + return fmt.Errorf("Got a bad http response status code from Eclair %d for request %s. Body: %s", resp.StatusCode, httpReq.URL, response) } return json.NewDecoder(resp.Body).Decode(response) } diff --git a/lnd/eclair_models.go b/lnd/eclair_models.go index dbc531a3..543f8f7c 100644 --- a/lnd/eclair_models.go +++ b/lnd/eclair_models.go @@ -212,3 +212,26 @@ type EclairChannel struct { } `json:"channelUpdate"` } `json:"data"` } + +type EclairInvoice struct { + Prefix string `json:"prefix"` + Timestamp int `json:"timestamp"` + NodeID string `json:"nodeId"` + Serialized string `json:"serialized"` + Description string `json:"description"` + PaymentHash string `json:"paymentHash"` + PaymentMetadata string `json:"paymentMetadata"` + Expiry int `json:"expiry"` + MinFinalCltvExpiry int `json:"minFinalCltvExpiry"` + Amount int `json:"amount"` + Features struct { + Activated struct { + PaymentSecret string `json:"payment_secret"` + BasicMpp string `json:"basic_mpp"` + OptionPaymentMetadata string `json:"option_payment_metadata"` + VarOnionOptin string `json:"var_onion_optin"` + } `json:"activated"` + Unknown []interface{} `json:"unknown"` + } `json:"features"` + RoutingInfo []interface{} `json:"routingInfo"` +} From 97d91f7c47f5d423053bc656420219dcb2fe1e49 Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Wed, 18 Jan 2023 16:33:48 +0100 Subject: [PATCH 05/15] eclair: add pay invoice and decode invoice --- controllers/payinvoice.ctrl.go | 2 +- lnd/eclair.go | 48 ++++++++++++++++++++++++---- lnd/eclair_models.go | 58 ++++++++++++++++++++++++++++++++++ 3 files changed, 100 insertions(+), 8 deletions(-) diff --git a/controllers/payinvoice.ctrl.go b/controllers/payinvoice.ctrl.go index b8242b8c..6209a4c0 100644 --- a/controllers/payinvoice.ctrl.go +++ b/controllers/payinvoice.ctrl.go @@ -100,7 +100,7 @@ func (controller *PayInvoiceController) PayInvoice(c echo.Context) error { controller.svc.DB.NewDelete().Model(&invoice).Where("id = ?", invoice.ID).Exec(c.Request().Context()) return c.JSON(http.StatusBadRequest, responses.NotEnoughBalanceError) } - + fmt.Println("got heree") sendPaymentResponse, err := controller.svc.PayInvoice(c.Request().Context(), invoice) if err != nil { c.Logger().Errorf("Payment failed invoice_id:%v user_id:%v error: %v", invoice.ID, userID, err) diff --git a/lnd/eclair.go b/lnd/eclair.go index 1ce4dcbd..0615945d 100644 --- a/lnd/eclair.go +++ b/lnd/eclair.go @@ -99,16 +99,34 @@ func (eclair *EclairClient) ListChannels(ctx context.Context, req *lnrpc.ListCha } func (eclair *EclairClient) SendPaymentSync(ctx context.Context, req *lnrpc.SendRequest, options ...grpc.CallOption) (*lnrpc.SendResponse, error) { - panic("not implemented") // TODO: Implement + payload := url.Values{} + payload.Add("invoice", req.PaymentRequest) + payload.Add("amountMsat", strconv.Itoa(int(req.Amt)*1000)) + payload.Add("maxFeeFlatSat", strconv.Itoa(int(req.FeeLimit.GetFixed()))) + payload.Add("blocking", "true") //wtf + resp := &EclairPayResponse{} + err := eclair.Request(ctx, http.MethodPost, "/payinvoice", "application/x-www-form-urlencoded", payload, resp) + if err != nil { + return nil, err + } + errString := "" + if resp.Type == "payment-failed" && len(resp.Failures) > 0 { + errString = resp.Failures[0].T + } + //todo sum all parts + return &lnrpc.SendResponse{ + PaymentError: errString, + PaymentPreimage: []byte(resp.PaymentPreimage), + PaymentRoute: &lnrpc.Route{ + TotalFees: int64(resp.Parts[0].FeesPaid), + TotalAmt: int64(resp.RecipientAmount) + int64(resp.Parts[0].FeesPaid), + }, + PaymentHash: []byte(resp.PaymentHash), + }, nil } func (eclair *EclairClient) AddInvoice(ctx context.Context, req *lnrpc.Invoice, options ...grpc.CallOption) (*lnrpc.AddInvoiceResponse, error) { payload := url.Values{} - ///Description: req.Memo, - ///DescriptionHash: string(req.DescriptionHash), - ///AmountMsat: req.Value, - ///ExpireIn: req.Expiry, - ///paymentPreimage: hex.EncodeToString(req.RPreimage), if req.Memo != "" { payload.Add("description", req.Memo) } @@ -195,5 +213,21 @@ func (eclair *EclairClient) Request(ctx context.Context, method, endpoint, conte } func (eclair *EclairClient) DecodeBolt11(ctx context.Context, bolt11 string, options ...grpc.CallOption) (*lnrpc.PayReq, error) { - panic("not implemented") // TODO: Implement + invoice := &EclairInvoice{} + payload := url.Values{} + payload.Add("invoice", bolt11) + err := eclair.Request(ctx, http.MethodPost, "/parseinvoice", "application/x-www-form-urlencoded", payload, invoice) + if err != nil { + return nil, err + } + return &lnrpc.PayReq{ + Destination: invoice.NodeID, + PaymentHash: invoice.PaymentHash, + NumSatoshis: int64(invoice.Amount) / 1000, + Timestamp: int64(invoice.Timestamp), + Expiry: int64(invoice.Expiry), + Description: invoice.Description, + DescriptionHash: invoice.DescriptionHash, + NumMsat: int64(invoice.Amount), + }, nil } diff --git a/lnd/eclair_models.go b/lnd/eclair_models.go index 543f8f7c..f14199ee 100644 --- a/lnd/eclair_models.go +++ b/lnd/eclair_models.go @@ -219,6 +219,7 @@ type EclairInvoice struct { NodeID string `json:"nodeId"` Serialized string `json:"serialized"` Description string `json:"description"` + DescriptionHash string `json:"descriptionHash"` PaymentHash string `json:"paymentHash"` PaymentMetadata string `json:"paymentMetadata"` Expiry int `json:"expiry"` @@ -235,3 +236,60 @@ type EclairInvoice struct { } `json:"features"` RoutingInfo []interface{} `json:"routingInfo"` } + +type EclairPayResponse struct { + Type string `json:"type"` + ID string `json:"id"` + PaymentHash string `json:"paymentHash"` + PaymentPreimage string `json:"paymentPreimage"` + RecipientAmount int `json:"recipientAmount"` + RecipientNodeID string `json:"recipientNodeId"` + Failures []struct { + Amount int64 `json:"amount"` + Route []interface{} `json:"route"` + T string `json:"t"` + } `json:"failures"` + Parts []struct { + ID string `json:"id"` + Amount int `json:"amount"` + FeesPaid int `json:"feesPaid"` + ToChannelID string `json:"toChannelId"` + Route []struct { + ShortChannelID string `json:"shortChannelId"` + NodeID string `json:"nodeId"` + NextNodeID string `json:"nextNodeId"` + Params struct { + Type string `json:"type"` + ChannelUpdate struct { + Signature string `json:"signature"` + ChainHash string `json:"chainHash"` + ShortChannelID string `json:"shortChannelId"` + Timestamp struct { + Iso time.Time `json:"iso"` + Unix int `json:"unix"` + } `json:"timestamp"` + MessageFlags struct { + DontForward bool `json:"dontForward"` + } `json:"messageFlags"` + ChannelFlags struct { + IsEnabled bool `json:"isEnabled"` + IsNode1 bool `json:"isNode1"` + } `json:"channelFlags"` + CltvExpiryDelta int `json:"cltvExpiryDelta"` + HtlcMinimumMsat int `json:"htlcMinimumMsat"` + FeeBaseMsat int `json:"feeBaseMsat"` + FeeProportionalMillionths int `json:"feeProportionalMillionths"` + HtlcMaximumMsat int64 `json:"htlcMaximumMsat"` + TlvStream struct { + Records []interface{} `json:"records"` + Unknown []interface{} `json:"unknown"` + } `json:"tlvStream"` + } `json:"channelUpdate"` + } `json:"params"` + } `json:"route"` + Timestamp struct { + Iso time.Time `json:"iso"` + Unix int `json:"unix"` + } `json:"timestamp"` + } `json:"parts"` +} From 165788cb87c5ec8230a6cafef77c930432b6c390 Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Tue, 18 Apr 2023 14:36:24 +0200 Subject: [PATCH 06/15] remove debug statement --- controllers/payinvoice.ctrl.go | 1 - 1 file changed, 1 deletion(-) diff --git a/controllers/payinvoice.ctrl.go b/controllers/payinvoice.ctrl.go index 6209a4c0..336bca30 100644 --- a/controllers/payinvoice.ctrl.go +++ b/controllers/payinvoice.ctrl.go @@ -100,7 +100,6 @@ func (controller *PayInvoiceController) PayInvoice(c echo.Context) error { controller.svc.DB.NewDelete().Model(&invoice).Where("id = ?", invoice.ID).Exec(c.Request().Context()) return c.JSON(http.StatusBadRequest, responses.NotEnoughBalanceError) } - fmt.Println("got heree") sendPaymentResponse, err := controller.svc.PayInvoice(c.Request().Context(), invoice) if err != nil { c.Logger().Errorf("Payment failed invoice_id:%v user_id:%v error: %v", invoice.ID, userID, err) From 5f2401a5c56b09c1b29325fe6c4bf95e018a7943 Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Tue, 18 Apr 2023 14:36:49 +0200 Subject: [PATCH 07/15] remove debug statement --- controllers/payinvoice.ctrl.go | 1 + 1 file changed, 1 insertion(+) diff --git a/controllers/payinvoice.ctrl.go b/controllers/payinvoice.ctrl.go index 336bca30..b8242b8c 100644 --- a/controllers/payinvoice.ctrl.go +++ b/controllers/payinvoice.ctrl.go @@ -100,6 +100,7 @@ func (controller *PayInvoiceController) PayInvoice(c echo.Context) error { controller.svc.DB.NewDelete().Model(&invoice).Where("id = ?", invoice.ID).Exec(c.Request().Context()) return c.JSON(http.StatusBadRequest, responses.NotEnoughBalanceError) } + sendPaymentResponse, err := controller.svc.PayInvoice(c.Request().Context(), invoice) if err != nil { c.Logger().Errorf("Payment failed invoice_id:%v user_id:%v error: %v", invoice.ID, userID, err) From 2bedfbd0ce09cf8dbdf40329a123ede68f2a351d Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Tue, 18 Apr 2023 14:39:45 +0200 Subject: [PATCH 08/15] don't require lnd host --- lib/service/config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/service/config.go b/lib/service/config.go index 1e80b55f..fac11baa 100644 --- a/lib/service/config.go +++ b/lib/service/config.go @@ -20,7 +20,7 @@ type Config struct { JWTRefreshTokenExpiry int `envconfig:"JWT_REFRESH_EXPIRY" default:"604800"` // in seconds, default 7 days JWTAccessTokenExpiry int `envconfig:"JWT_ACCESS_EXPIRY" default:"172800"` // in seconds, default 2 days LNClientType string `envconfig:"LN_CLIENT_TYPE" default:"lnd"` //lnd, eclair? - LNDAddress string `envconfig:"LND_ADDRESS" required:"true"` + LNDAddress string `envconfig:"LND_ADDRESS"` LNDMacaroonFile string `envconfig:"LND_MACAROON_FILE"` LNDCertFile string `envconfig:"LND_CERT_FILE"` LNDMacaroonHex string `envconfig:"LND_MACAROON_HEX"` From 00ea5b49b15be5ce9f71927697c02a5fc4d8b129 Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Tue, 18 Apr 2023 14:42:54 +0200 Subject: [PATCH 09/15] no unneeded changes --- lib/service/config.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/service/config.go b/lib/service/config.go index fac11baa..5e22441f 100644 --- a/lib/service/config.go +++ b/lib/service/config.go @@ -44,17 +44,17 @@ type Config struct { MaxReceiveAmount int64 `envconfig:"MAX_RECEIVE_AMOUNT" default:"0"` MaxSendAmount int64 `envconfig:"MAX_SEND_AMOUNT" default:"0"` MaxAccountBalance int64 `envconfig:"MAX_ACCOUNT_BALANCE" default:"0"` + RabbitMQUri string `envconfig:"RABBITMQ_URI"` + RabbitMQLndhubInvoiceExchange string `envconfig:"RABBITMQ_INVOICE_EXCHANGE" default:"lndhub_invoice"` + RabbitMQLndInvoiceExchange string `envconfig:"RABBITMQ_LND_INVOICE_EXCHANGE" default:"lnd_invoice"` + RabbitMQInvoiceConsumerQueueName string `envconfig:"RABBITMQ_INVOICE_CONSUMER_QUEUE_NAME" default:"lnd_invoice_consumer"` + SubscriptionConsumerType string `envconfig:"SUBSCRIPTION_CONSUMER_TYPE" default:"grpc"` Branding BrandingConfig DatabaseMaxConns int `envconfig:"DATABASE_MAX_CONNS" default:"10"` DatabaseMaxIdleConns int `envconfig:"DATABASE_MAX_IDLE_CONNS" default:"5"` DatabaseConnMaxLifetime int `envconfig:"DATABASE_CONN_MAX_LIFETIME" default:"1800"` // 30 minutes DatabaseTimeout int `envconfig:"DATABASE_TIMEOUT" default:"60"` // 60 seconds DatadogAgentUrl string `envconfig:"DATADOG_AGENT_URL"` - RabbitMQUri string `envconfig:"RABBITMQ_URI"` - RabbitMQLndhubInvoiceExchange string `envconfig:"RABBITMQ_INVOICE_EXCHANGE" default:"lndhub_invoice"` - RabbitMQLndInvoiceExchange string `envconfig:"RABBITMQ_LND_INVOICE_EXCHANGE" default:"lnd_invoice"` - RabbitMQInvoiceConsumerQueueName string `envconfig:"RABBITMQ_INVOICE_CONSUMER_QUEUE_NAME" default:"lnd_invoice_consumer"` - SubscriptionConsumerType string `envconfig:"SUBSCRIPTION_CONSUMER_TYPE" default:"grpc"` } type BrandingConfig struct { From 144aa2a3569d9f70cf0ff912a86532e8dee7a29c Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Tue, 18 Apr 2023 14:45:17 +0200 Subject: [PATCH 10/15] no unneeded changes --- lib/service/config.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/service/config.go b/lib/service/config.go index 5e22441f..6df3e492 100644 --- a/lib/service/config.go +++ b/lib/service/config.go @@ -12,7 +12,12 @@ const ( type Config struct { DatabaseUri string `envconfig:"DATABASE_URI" required:"true"` + DatabaseMaxConns int `envconfig:"DATABASE_MAX_CONNS" default:"10"` + DatabaseMaxIdleConns int `envconfig:"DATABASE_MAX_IDLE_CONNS" default:"5"` + DatabaseConnMaxLifetime int `envconfig:"DATABASE_CONN_MAX_LIFETIME" default:"1800"` // 30 minutes + DatabaseTimeout int `envconfig:"DATABASE_TIMEOUT" default:"60"` // 60 seconds SentryDSN string `envconfig:"SENTRY_DSN"` + DatadogAgentUrl string `envconfig:"DATADOG_AGENT_URL"` SentryTracesSampleRate float64 `envconfig:"SENTRY_TRACES_SAMPLE_RATE"` LogFilePath string `envconfig:"LOG_FILE_PATH"` JWTSecret []byte `envconfig:"JWT_SECRET" required:"true"` @@ -50,11 +55,6 @@ type Config struct { RabbitMQInvoiceConsumerQueueName string `envconfig:"RABBITMQ_INVOICE_CONSUMER_QUEUE_NAME" default:"lnd_invoice_consumer"` SubscriptionConsumerType string `envconfig:"SUBSCRIPTION_CONSUMER_TYPE" default:"grpc"` Branding BrandingConfig - DatabaseMaxConns int `envconfig:"DATABASE_MAX_CONNS" default:"10"` - DatabaseMaxIdleConns int `envconfig:"DATABASE_MAX_IDLE_CONNS" default:"5"` - DatabaseConnMaxLifetime int `envconfig:"DATABASE_CONN_MAX_LIFETIME" default:"1800"` // 30 minutes - DatabaseTimeout int `envconfig:"DATABASE_TIMEOUT" default:"60"` // 60 seconds - DatadogAgentUrl string `envconfig:"DATADOG_AGENT_URL"` } type BrandingConfig struct { From e368d46a51499435e6dcb501e3ea4d1e95863621 Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Wed, 19 Apr 2023 11:00:11 +0200 Subject: [PATCH 11/15] fix hex decode bug --- lnd/eclair.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lnd/eclair.go b/lnd/eclair.go index 0615945d..ff50987d 100644 --- a/lnd/eclair.go +++ b/lnd/eclair.go @@ -141,8 +141,12 @@ func (eclair *EclairClient) AddInvoice(ctx context.Context, req *lnrpc.Invoice, if err != nil { return nil, err } + rHash, err := hex.DecodeString(invoice.PaymentHash) + if err != nil { + return nil, err + } return &lnrpc.AddInvoiceResponse{ - RHash: []byte(invoice.PaymentHash), + RHash: rHash, PaymentRequest: invoice.Serialized, AddIndex: uint64(invoice.Timestamp), }, nil From 35868ba6aa7d793294754eb2282d4fbe8382025c Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Mon, 8 May 2023 08:56:36 +0200 Subject: [PATCH 12/15] fix msat bug --- lnd/eclair.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lnd/eclair.go b/lnd/eclair.go index ff50987d..a8c55544 100644 --- a/lnd/eclair.go +++ b/lnd/eclair.go @@ -113,13 +113,16 @@ func (eclair *EclairClient) SendPaymentSync(ctx context.Context, req *lnrpc.Send if resp.Type == "payment-failed" && len(resp.Failures) > 0 { errString = resp.Failures[0].T } - //todo sum all parts + totalFees := 0 + for _, part := range resp.Parts { + totalFees += part.FeesPaid / 1000 + } return &lnrpc.SendResponse{ PaymentError: errString, PaymentPreimage: []byte(resp.PaymentPreimage), PaymentRoute: &lnrpc.Route{ - TotalFees: int64(resp.Parts[0].FeesPaid), - TotalAmt: int64(resp.RecipientAmount) + int64(resp.Parts[0].FeesPaid), + TotalFees: int64(totalFees), + TotalAmt: int64(resp.RecipientAmount)/1000 + int64(totalFees), }, PaymentHash: []byte(resp.PaymentHash), }, nil From 98d7ae14f369026ce133c3f283b279b14757f58c Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Fri, 11 Aug 2023 14:02:48 +0200 Subject: [PATCH 13/15] fix merge conflict --- lib/service/config.go | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/lib/service/config.go b/lib/service/config.go index e84c5a2f..38ca101a 100644 --- a/lib/service/config.go +++ b/lib/service/config.go @@ -6,14 +6,9 @@ import ( ) const ( -<<<<<<< HEAD - LND_CLIENT_TYPE = "lnd" - ECLAIR_CLIENT_TYPE = "eclair" -======= LND_CLIENT_TYPE = "lnd" LND_CLUSTER_CLIENT_TYPE = "lnd_cluster" ECLAIR_CLIENT_TYPE = "eclair" ->>>>>>> main ) type Config struct { @@ -30,24 +25,16 @@ type Config struct { AdminToken string `envconfig:"ADMIN_TOKEN"` JWTRefreshTokenExpiry int `envconfig:"JWT_REFRESH_EXPIRY" default:"604800"` // in seconds, default 7 days JWTAccessTokenExpiry int `envconfig:"JWT_ACCESS_EXPIRY" default:"172800"` // in seconds, default 2 days -<<<<<<< HEAD - LNClientType string `envconfig:"LN_CLIENT_TYPE" default:"lnd"` //lnd, eclair? - LNDAddress string `envconfig:"LND_ADDRESS"` -======= LNClientType string `envconfig:"LN_CLIENT_TYPE" default:"lnd"` //lnd, lnd_cluster, eclair LNDAddress string `envconfig:"LND_ADDRESS" required:"true"` ->>>>>>> main LNDMacaroonFile string `envconfig:"LND_MACAROON_FILE"` LNDCertFile string `envconfig:"LND_CERT_FILE"` LNDMacaroonHex string `envconfig:"LND_MACAROON_HEX"` LNDCertHex string `envconfig:"LND_CERT_HEX"` -<<<<<<< HEAD EclairHost string `envconfig:"ECLAIR_HOST"` EclairPassword string `envconfig:"ECLAIR_PASSWORD"` -======= LNDClusterLivenessPeriod int `envconfig:"LND_CLUSTER_LIVENESS_PERIOD" default:"10"` LNDClusterActiveChannelRatio float64 `envconfig:"LND_CLUSTER_ACTIVE_CHANNEL_RATIO" default:"0.5"` ->>>>>>> main CustomName string `envconfig:"CUSTOM_NAME"` Host string `envconfig:"HOST" default:"localhost:3000"` Port int `envconfig:"PORT" default:"3000"` From e3d1a75b58667c5c993334ee9e99312684672f90 Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Fri, 11 Aug 2023 14:09:00 +0200 Subject: [PATCH 14/15] fix preimage decode --- lnd/eclair.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lnd/eclair.go b/lnd/eclair.go index a8c55544..9a35ab6e 100644 --- a/lnd/eclair.go +++ b/lnd/eclair.go @@ -117,9 +117,13 @@ func (eclair *EclairClient) SendPaymentSync(ctx context.Context, req *lnrpc.Send for _, part := range resp.Parts { totalFees += part.FeesPaid / 1000 } + preimage, err := hex.DecodeString(resp.PaymentPreimage) + if err != nil { + return nil, err + } return &lnrpc.SendResponse{ PaymentError: errString, - PaymentPreimage: []byte(resp.PaymentPreimage), + PaymentPreimage: preimage, PaymentRoute: &lnrpc.Route{ TotalFees: int64(totalFees), TotalAmt: int64(resp.RecipientAmount)/1000 + int64(totalFees), From a8fe18a2bc1292029fc40be96ef9bab520318fbe Mon Sep 17 00:00:00 2001 From: kiwiidb Date: Fri, 11 Aug 2023 14:37:27 +0200 Subject: [PATCH 15/15] fix eclair init --- init_lnd.go | 2 ++ lib/service/config.go | 1 - lnd/eclair.go | 23 +++++++++++++++++++---- 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/init_lnd.go b/init_lnd.go index b3618ba3..3f038b9e 100644 --- a/init_lnd.go +++ b/init_lnd.go @@ -17,6 +17,8 @@ func InitLNClient(c *service.Config, logger *lecho.Logger, ctx context.Context) return InitSingleLNDClient(c, ctx) case service.LND_CLUSTER_CLIENT_TYPE: return InitLNDCluster(c, logger, ctx) + case service.ECLAIR_CLIENT_TYPE: + return lnd.NewEclairClient(c.LNDAddress, c.EclairPassword, ctx) default: return nil, fmt.Errorf("Did not recognize LN client type %s", c.LNClientType) } diff --git a/lib/service/config.go b/lib/service/config.go index 38ca101a..8163c4d4 100644 --- a/lib/service/config.go +++ b/lib/service/config.go @@ -31,7 +31,6 @@ type Config struct { LNDCertFile string `envconfig:"LND_CERT_FILE"` LNDMacaroonHex string `envconfig:"LND_MACAROON_HEX"` LNDCertHex string `envconfig:"LND_CERT_HEX"` - EclairHost string `envconfig:"ECLAIR_HOST"` EclairPassword string `envconfig:"ECLAIR_PASSWORD"` LNDClusterLivenessPeriod int `envconfig:"LND_CLUSTER_LIVENESS_PERIOD" default:"10"` LNDClusterActiveChannelRatio float64 `envconfig:"LND_CLUSTER_ACTIVE_CHANNEL_RATIO" default:"0.5"` diff --git a/lnd/eclair.go b/lnd/eclair.go index 9a35ab6e..1c4b79f7 100644 --- a/lnd/eclair.go +++ b/lnd/eclair.go @@ -16,8 +16,9 @@ import ( ) type EclairClient struct { - host string - password string + host string + password string + IdentityPubkey string } type EclairInvoicesSubscriber struct { @@ -42,11 +43,17 @@ func (ept *EclairPaymentsTracker) Recv() (*lnrpc.Payment, error) { return nil, fmt.Errorf("context canceled") } -func NewEclairClient(host, password string) *EclairClient { - return &EclairClient{ +func NewEclairClient(host, password string, ctx context.Context) (result *EclairClient, err error) { + result = &EclairClient{ host: host, password: password, } + info, err := result.GetInfo(ctx, &lnrpc.GetInfoRequest{}) + if err != nil { + return nil, err + } + result.IdentityPubkey = info.IdentityPubkey + return result, nil } func (eclair *EclairClient) ListChannels(ctx context.Context, req *lnrpc.ListChannelsRequest, options ...grpc.CallOption) (*lnrpc.ListChannelsResponse, error) { @@ -242,3 +249,11 @@ func (eclair *EclairClient) DecodeBolt11(ctx context.Context, bolt11 string, opt NumMsat: int64(invoice.Amount), }, nil } + +func (eclair *EclairClient) IsIdentityPubkey(pubkey string) (isOurPubkey bool) { + return pubkey == eclair.IdentityPubkey +} + +func (eclair *EclairClient) GetMainPubkey() (pubkey string) { + return eclair.IdentityPubkey +}