From 11a04e65ffb401eadcb6a7f68ff57d293002e6be Mon Sep 17 00:00:00 2001 From: carla Date: Wed, 17 Jun 2020 08:44:43 +0200 Subject: [PATCH 1/7] lndclient: add initiator, private and uptime fields to listchannels --- lndclient/lightning_client.go | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/lndclient/lightning_client.go b/lndclient/lightning_client.go index b22f4cc..77f3b43 100644 --- a/lndclient/lightning_client.go +++ b/lndclient/lightning_client.go @@ -71,7 +71,10 @@ type Info struct { // ChannelInfo stores unpacked per-channel info. type ChannelInfo struct { - // Active indecates whether the channel is active. + // ChannelPoint is the funding outpoint of the channel. + ChannelPoint string + + // Active indicates whether the channel is active. Active bool // ChannelID holds the unique channel ID for the channel. The first 3 bytes @@ -90,6 +93,20 @@ type ChannelInfo struct { // RemoteBalance is the counterparty's current balance in this channel. RemoteBalance btcutil.Amount + + // Initiator indicates whether we opened the channel or not. + Initiator bool + + // Private indicates that the channel is private. + Private bool + + // LifeTime is the total amount of time we have monitored the peer's + // online status for. + LifeTime time.Duration + + // Uptime is the total amount of time the peer has been observed as + // online over its lifetime. + Uptime time.Duration } var ( @@ -545,12 +562,21 @@ func (s *lightningClient) ListChannels(ctx context.Context) ( } result[i] = ChannelInfo{ + ChannelPoint: channel.ChannelPoint, Active: channel.Active, ChannelID: channel.ChanId, PubKeyBytes: remoteVertex, Capacity: btcutil.Amount(channel.Capacity), LocalBalance: btcutil.Amount(channel.LocalBalance), RemoteBalance: btcutil.Amount(channel.RemoteBalance), + Initiator: channel.Initiator, + Private: channel.Private, + LifeTime: time.Second * time.Duration( + channel.Lifetime, + ), + Uptime: time.Second * time.Duration( + channel.Uptime, + ), } } From c1a9889e4d722fe57c82323facaedd847bc61617 Mon Sep 17 00:00:00 2001 From: carla Date: Wed, 17 Jun 2020 08:44:43 +0200 Subject: [PATCH 2/7] lndclient: add closed channels to lnd client --- lndclient/lightning_client.go | 259 ++++++++++++++++++++++++++++++++++ test/lightning_client_mock.go | 7 + test/lnd_services_mock.go | 3 +- 3 files changed, 268 insertions(+), 1 deletion(-) diff --git a/lndclient/lightning_client.go b/lndclient/lightning_client.go index 77f3b43..2506769 100644 --- a/lndclient/lightning_client.go +++ b/lndclient/lightning_client.go @@ -50,6 +50,9 @@ type LightningClient interface { // ListChannels retrieves all channels of the backing lnd node. ListChannels(ctx context.Context) ([]ChannelInfo, error) + // ClosedChannels returns all closed channels of the backing lnd node. + ClosedChannels(ctx context.Context) ([]ClosedChannel, error) + // ChannelBackup retrieves the backup for a particular channel. The // backup is returned as an encrypted chanbackup.Single payload. ChannelBackup(context.Context, wire.OutPoint) ([]byte, error) @@ -109,6 +112,138 @@ type ChannelInfo struct { Uptime time.Duration } +// ClosedChannel represents a channel that has been closed. +type ClosedChannel struct { + // ChannelPoint is the funding outpoint of the channel. + ChannelPoint string + + // ChannelID holds the unique channel ID for the channel. The first 3 + // bytes are the block height, the next 3 the index within the block, + // and the last 2 bytes are the output index for the channel. + ChannelID uint64 + + // ClosingTxHash is the tx hash of the close transaction for the channel. + ClosingTxHash string + + // CloseType is the type of channel closure. + CloseType CloseType + + // OpenInitiator is true if we opened the channel. This value is not + // always available (older channels do not have it). + OpenInitiator Initiator + + // Initiator indicates which party initiated the channel close. Since + // this value is not always set in the rpc response, we also make a best + // effort attempt to set it based on CloseType. + CloseInitiator Initiator + + // PubKeyBytes is the raw bytes of the public key of the remote node. + PubKeyBytes route.Vertex + + // Capacity is the total amount of funds held in this channel. + Capacity btcutil.Amount + + // SettledBalance is the amount we were paid out directly in this + // channel close. Note that this does not include cases where we need to + // sweep our commitment or htlcs. + SettledBalance btcutil.Amount +} + +// CloseType is an enum which represents the types of closes our channels may +// have. This type maps to the rpc value. +type CloseType uint8 + +const ( + // CloseTypeCooperative represents cooperative closes. + CloseTypeCooperative CloseType = iota + + // CloseTypeLocalForce represents force closes that we initiated. + CloseTypeLocalForce + + // CloseTypeRemoteForce represents force closes that our peer initiated. + CloseTypeRemoteForce + + // CloseTypeBreach represents breach closes from our peer. + CloseTypeBreach + + // CloseTypeFundingCancelled represents channels which were never + // created because their funding transaction was cancelled. + CloseTypeFundingCancelled + + // CloseTypeAbandoned represents a channel that was abandoned. + CloseTypeAbandoned +) + +// String returns the string representation of a close type. +func (c CloseType) String() string { + switch c { + case CloseTypeCooperative: + return "Cooperative" + + case CloseTypeLocalForce: + return "Local Force" + + case CloseTypeRemoteForce: + return "Remote Force" + + case CloseTypeBreach: + return "Breach" + + case CloseTypeFundingCancelled: + return "Funding Cancelled" + + case CloseTypeAbandoned: + return "Abandoned" + + default: + return "Unknown" + } +} + +// Initiator indicates the party that opened or closed a channel. This enum is +// used for cases where we may not have a full set of initiator information +// available over rpc (this is the case for older channels). +type Initiator uint8 + +const ( + // InitiatorUnrecorded is set when we do not know the open/close + // initiator for a channel, this is the case when the channel was + // closed before lnd started tracking initiators. + InitiatorUnrecorded Initiator = iota + + // InitiatorLocal is set when we initiated a channel open or close. + InitiatorLocal + + // InitiatorRemote is set when the remote party initiated a chanel open + // or close. + InitiatorRemote + + // InitiatorBoth is set in the case where both parties initiated a + // cooperative close (this is possible with multiple rounds of + // negotiation). + InitiatorBoth +) + +// String provides the string represenetation of a close initiator. +func (c Initiator) String() string { + switch c { + case InitiatorUnrecorded: + return "Unrecorded" + + case InitiatorLocal: + return "Local" + + case InitiatorRemote: + return "Remote" + + case InitiatorBoth: + return "Both" + + default: + return fmt.Sprintf("unknown initiator: %d", c) + } +} + var ( // ErrMalformedServerResponse is returned when the swap and/or prepay // invoice is malformed. @@ -583,6 +718,130 @@ func (s *lightningClient) ListChannels(ctx context.Context) ( return result, nil } +// ClosedChannels returns a list of our closed channels. +func (s *lightningClient) ClosedChannels(ctx context.Context) ([]ClosedChannel, + error) { + + rpcCtx, cancel := context.WithTimeout(ctx, rpcTimeout) + defer cancel() + + response, err := s.client.ClosedChannels( + s.adminMac.WithMacaroonAuth(rpcCtx), + &lnrpc.ClosedChannelsRequest{}, + ) + if err != nil { + return nil, err + } + + channels := make([]ClosedChannel, len(response.Channels)) + for i, channel := range response.Channels { + remote, err := route.NewVertexFromStr(channel.RemotePubkey) + if err != nil { + return nil, err + } + + closeType, err := rpcCloseType(channel.CloseType) + if err != nil { + return nil, err + } + + openInitiator, err := getInitiator(channel.OpenInitiator) + if err != nil { + return nil, err + } + + closeInitiator, err := rpcCloseInitiator( + channel.CloseInitiator, closeType, + ) + if err != nil { + return nil, err + } + + channels[i] = ClosedChannel{ + ChannelPoint: channel.ChannelPoint, + ChannelID: channel.ChanId, + ClosingTxHash: channel.ClosingTxHash, + CloseType: closeType, + OpenInitiator: openInitiator, + CloseInitiator: closeInitiator, + PubKeyBytes: remote, + Capacity: btcutil.Amount(channel.Capacity), + SettledBalance: btcutil.Amount(channel.SettledBalance), + } + } + + return channels, nil +} + +// rpcCloseType maps a rpc close type to our local enum. +func rpcCloseType(t lnrpc.ChannelCloseSummary_ClosureType) (CloseType, error) { + switch t { + case lnrpc.ChannelCloseSummary_COOPERATIVE_CLOSE: + return CloseTypeCooperative, nil + + case lnrpc.ChannelCloseSummary_LOCAL_FORCE_CLOSE: + return CloseTypeLocalForce, nil + + case lnrpc.ChannelCloseSummary_REMOTE_FORCE_CLOSE: + return CloseTypeRemoteForce, nil + + case lnrpc.ChannelCloseSummary_BREACH_CLOSE: + return CloseTypeBreach, nil + + case lnrpc.ChannelCloseSummary_FUNDING_CANCELED: + return CloseTypeFundingCancelled, nil + + case lnrpc.ChannelCloseSummary_ABANDONED: + return CloseTypeAbandoned, nil + + default: + return 0, fmt.Errorf("unknown close type: %v", t) + } +} + +// rpcCloseInitiator maps a close initiator to our local type. Since this field +// is not always set in lnd for older channels, also use our close type to infer +// who initiated the close when we have force closes. +func rpcCloseInitiator(initiator lnrpc.Initiator, + closeType CloseType) (Initiator, error) { + + // Since our close type is always set on the rpc, we first check whether + // we can figure out the close initiator from this value. This is only + // possible for force closes/breaches. + switch closeType { + case CloseTypeLocalForce: + return InitiatorLocal, nil + + case CloseTypeRemoteForce, CloseTypeBreach: + return InitiatorRemote, nil + } + + // Otherwise, we check whether our initiator field is set, and fail only + // if we have an unknown type. + return getInitiator(initiator) +} + +// getInitiator maps a rpc initiator value to our initiator enum. +func getInitiator(initiator lnrpc.Initiator) (Initiator, error) { + switch initiator { + case lnrpc.Initiator_INITIATOR_LOCAL: + return InitiatorLocal, nil + + case lnrpc.Initiator_INITIATOR_REMOTE: + return InitiatorRemote, nil + + case lnrpc.Initiator_INITIATOR_BOTH: + return InitiatorBoth, nil + + case lnrpc.Initiator_INITIATOR_UNKNOWN: + return InitiatorUnrecorded, nil + + default: + return InitiatorUnrecorded, fmt.Errorf("unknown "+ + "initiator: %v", initiator) + } +} + // ChannelBackup retrieves the backup for a particular channel. The backup is // returned as an encrypted chanbackup.Single payload. func (s *lightningClient) ChannelBackup(ctx context.Context, diff --git a/test/lightning_client_mock.go b/test/lightning_client_mock.go index 2bac092..e030afe 100644 --- a/test/lightning_client_mock.go +++ b/test/lightning_client_mock.go @@ -175,6 +175,13 @@ func (h *mockLightningClient) ListChannels(ctx context.Context) ( return h.lnd.Channels, nil } +// ClosedChannels returns a list of our closed channels. +func (h *mockLightningClient) ClosedChannels(_ context.Context) ([]lndclient.ClosedChannel, + error) { + + return h.lnd.ClosedChannels, nil +} + // ChannelBackup retrieves the backup for a particular channel. The // backup is returned as an encrypted chanbackup.Single payload. func (h *mockLightningClient) ChannelBackup(context.Context, wire.OutPoint) ([]byte, error) { diff --git a/test/lnd_services_mock.go b/test/lnd_services_mock.go index b02769c..7c48baf 100644 --- a/test/lnd_services_mock.go +++ b/test/lnd_services_mock.go @@ -163,7 +163,8 @@ type LndMockServices struct { // keyed by hash string. Invoices map[lntypes.Hash]*lndclient.Invoice - Channels []lndclient.ChannelInfo + Channels []lndclient.ChannelInfo + ClosedChannels []lndclient.ClosedChannel WaitForFinished func() From e016d2ebf4d15045d65c67044b11bcf8cd7fb8a7 Mon Sep 17 00:00:00 2001 From: carla Date: Wed, 17 Jun 2020 08:44:43 +0200 Subject: [PATCH 3/7] lndclient: add forwarding history call --- lndclient/lightning_client.go | 94 +++++++++++++++++++++++++++++++++++ test/lightning_client_mock.go | 11 ++++ test/lnd_services_mock.go | 5 +- 3 files changed, 108 insertions(+), 2 deletions(-) diff --git a/lndclient/lightning_client.go b/lndclient/lightning_client.go index 2506769..b948b98 100644 --- a/lndclient/lightning_client.go +++ b/lndclient/lightning_client.go @@ -53,6 +53,11 @@ type LightningClient interface { // ClosedChannels returns all closed channels of the backing lnd node. ClosedChannels(ctx context.Context) ([]ClosedChannel, error) + // ForwardingHistory makes a paginated call to our forwarding history + // endpoint. + ForwardingHistory(ctx context.Context, + req ForwardingHistoryRequest) (*ForwardingHistoryResponse, error) + // ChannelBackup retrieves the backup for a particular channel. The // backup is returned as an encrypted chanbackup.Single payload. ChannelBackup(context.Context, wire.OutPoint) ([]byte, error) @@ -842,6 +847,95 @@ func getInitiator(initiator lnrpc.Initiator) (Initiator, error) { } } +// ForwardingHistoryRequest contains the request parameters for a paginated +// forwarding history call. +type ForwardingHistoryRequest struct { + // StartTime is the beginning of the query period. + StartTime time.Time + + // EndTime is the end of the query period. + EndTime time.Time + + // MaxEvents is the maximum number of events to return. + MaxEvents uint32 + + // Offset is the index from which to start querying. + Offset uint32 +} + +// ForwardingHistoryResponse contains the response to a forwarding history +// query, including last index offset required for paginated queries. +type ForwardingHistoryResponse struct { + // LastIndexOffset is the index offset of the last item in our set. + LastIndexOffset uint32 + + // Events is the set of events that were found in the interval queried. + Events []ForwardingEvent +} + +// ForwardingEvent represents a htlc that was forwarded through our node. +type ForwardingEvent struct { + // Timestamp is the time that we processed the forwarding event. + Timestamp time.Time + + // ChannelIn is the id of the channel the htlc arrived at our node on. + ChannelIn uint64 + + // ChannelOut is the id of the channel the htlc left our node on. + ChannelOut uint64 + + // AmountMsatIn is the amount that was forwarded into our node in + // millisatoshis. + AmountMsatIn lnwire.MilliSatoshi + + // AmountMsatOut is the amount that was forwarded out of our node in + // millisatoshis. + AmountMsatOut lnwire.MilliSatoshi + + // FeeMsat is the amount of fees earned in millisatoshis, + FeeMsat lnwire.MilliSatoshi +} + +// ForwardingHistory returns a set of forwarding events for the period queried. +// Note that this call is paginated, and the information required to make +// subsequent calls is provided in the response. +func (s *lightningClient) ForwardingHistory(ctx context.Context, + req ForwardingHistoryRequest) (*ForwardingHistoryResponse, error) { + + rpcCtx, cancel := context.WithTimeout(ctx, rpcTimeout) + defer cancel() + + response, err := s.client.ForwardingHistory( + s.adminMac.WithMacaroonAuth(rpcCtx), + &lnrpc.ForwardingHistoryRequest{ + StartTime: uint64(req.StartTime.Unix()), + EndTime: uint64(req.EndTime.Unix()), + IndexOffset: req.Offset, + NumMaxEvents: req.MaxEvents, + }, + ) + if err != nil { + return nil, err + } + + events := make([]ForwardingEvent, len(response.ForwardingEvents)) + for i, event := range response.ForwardingEvents { + events[i] = ForwardingEvent{ + Timestamp: time.Unix(int64(event.Timestamp), 0), + ChannelIn: event.ChanIdIn, + ChannelOut: event.ChanIdOut, + AmountMsatIn: lnwire.MilliSatoshi(event.AmtIn), + AmountMsatOut: lnwire.MilliSatoshi(event.AmtOut), + FeeMsat: lnwire.MilliSatoshi(event.FeeMsat), + } + } + + return &ForwardingHistoryResponse{ + LastIndexOffset: response.LastOffsetIndex, + Events: events, + }, nil +} + // ChannelBackup retrieves the backup for a particular channel. The backup is // returned as an encrypted chanbackup.Single payload. func (s *lightningClient) ChannelBackup(ctx context.Context, diff --git a/test/lightning_client_mock.go b/test/lightning_client_mock.go index e030afe..37c4a9d 100644 --- a/test/lightning_client_mock.go +++ b/test/lightning_client_mock.go @@ -182,6 +182,17 @@ func (h *mockLightningClient) ClosedChannels(_ context.Context) ([]lndclient.Clo return h.lnd.ClosedChannels, nil } +// ForwardingHistory returns the mock's set of forwarding events. +func (h *mockLightningClient) ForwardingHistory(_ context.Context, + _ lndclient.ForwardingHistoryRequest) (*lndclient.ForwardingHistoryResponse, + error) { + + return &lndclient.ForwardingHistoryResponse{ + LastIndexOffset: 0, + Events: h.lnd.ForwardingEvents, + }, nil +} + // ChannelBackup retrieves the backup for a particular channel. The // backup is returned as an encrypted chanbackup.Single payload. func (h *mockLightningClient) ChannelBackup(context.Context, wire.OutPoint) ([]byte, error) { diff --git a/test/lnd_services_mock.go b/test/lnd_services_mock.go index 7c48baf..cf08715 100644 --- a/test/lnd_services_mock.go +++ b/test/lnd_services_mock.go @@ -163,8 +163,9 @@ type LndMockServices struct { // keyed by hash string. Invoices map[lntypes.Hash]*lndclient.Invoice - Channels []lndclient.ChannelInfo - ClosedChannels []lndclient.ClosedChannel + Channels []lndclient.ChannelInfo + ClosedChannels []lndclient.ClosedChannel + ForwardingEvents []lndclient.ForwardingEvent WaitForFinished func() From 5ca018613bbdfc375d1848d1dd303281aa717e5b Mon Sep 17 00:00:00 2001 From: carla Date: Wed, 17 Jun 2020 08:44:43 +0200 Subject: [PATCH 4/7] lndclient: add listinvoices to client --- lndclient/lightning_client.go | 88 ++++++++++++++++++++++++++++++++++- test/lightning_client_mock.go | 15 ++++++ 2 files changed, 102 insertions(+), 1 deletion(-) diff --git a/lndclient/lightning_client.go b/lndclient/lightning_client.go index b948b98..244a78d 100644 --- a/lndclient/lightning_client.go +++ b/lndclient/lightning_client.go @@ -58,6 +58,10 @@ type LightningClient interface { ForwardingHistory(ctx context.Context, req ForwardingHistoryRequest) (*ForwardingHistoryResponse, error) + // ListInvoices makes a paginated call to our list invoices endpoint. + ListInvoices(ctx context.Context, req ListInvoicesRequest) ( + *ListInvoicesResponse, error) + // ChannelBackup retrieves the backup for a particular channel. The // backup is returned as an encrypted chanbackup.Single payload. ChannelBackup(context.Context, wire.OutPoint) ([]byte, error) @@ -606,6 +610,21 @@ func (s *lightningClient) LookupInvoice(ctx context.Context, return nil, err } + invoice, err := unmarshalInvoice(resp) + if err != nil { + return nil, err + } + + return invoice, nil +} + +// unmarshalInvoice creates an invoice from the rpc response provided. +func unmarshalInvoice(resp *lnrpc.Invoice) (*Invoice, error) { + hash, err := lntypes.MakeHash(resp.RHash) + if err != nil { + return nil, err + } + invoice := &Invoice{ Preimage: nil, Hash: hash, @@ -638,7 +657,8 @@ func (s *lightningClient) LookupInvoice(ctx context.Context, invoice.State = channeldb.ContractCanceled default: - return nil, fmt.Errorf("unknown invoice state: %v", resp.State) + return nil, fmt.Errorf("unknown invoice state: %v", + resp.State) } // Only set settle date if it is non-zero, because 0 unix time is @@ -936,6 +956,72 @@ func (s *lightningClient) ForwardingHistory(ctx context.Context, }, nil } +// ListInvoicesRequest contains the request parameters for a paginated +// list invoices call. +type ListInvoicesRequest struct { + // MaxInvoices is the maximum number of invoices to return. + MaxInvoices uint64 + + // Offset is the index from which to start querying. + Offset uint64 + + // Reversed is set to query our invoices backwards. + Reversed bool + + // PendingOnly is set if we only want pending invoices. + PendingOnly bool +} + +// ListInvoicesResponse contains the response to a list invoices query, +// including the index offsets required for paginated queries. +type ListInvoicesResponse struct { + // FirstIndexOffset is the index offset of the first item in our set. + FirstIndexOffset uint64 + + // LastIndexOffset is the index offset of the last item in our set. + LastIndexOffset uint64 + + // Invoices is the set of invoices that were returned. + Invoices []Invoice +} + +// ListInvoices returns a list of invoices from our node. +func (s *lightningClient) ListInvoices(ctx context.Context, + req ListInvoicesRequest) (*ListInvoicesResponse, error) { + + rpcCtx, cancel := context.WithTimeout(ctx, rpcTimeout) + defer cancel() + + resp, err := s.client.ListInvoices( + s.adminMac.WithMacaroonAuth(rpcCtx), + &lnrpc.ListInvoiceRequest{ + PendingOnly: false, + IndexOffset: req.Offset, + NumMaxInvoices: req.MaxInvoices, + Reversed: req.Reversed, + }, + ) + if err != nil { + return nil, err + } + + invoices := make([]Invoice, len(resp.Invoices)) + for i, invoice := range resp.Invoices { + inv, err := unmarshalInvoice(invoice) + if err != nil { + return nil, err + } + + invoices[i] = *inv + } + + return &ListInvoicesResponse{ + FirstIndexOffset: resp.FirstIndexOffset, + LastIndexOffset: resp.LastIndexOffset, + Invoices: invoices, + }, nil +} + // ChannelBackup retrieves the backup for a particular channel. The backup is // returned as an encrypted chanbackup.Single payload. func (s *lightningClient) ChannelBackup(ctx context.Context, diff --git a/test/lightning_client_mock.go b/test/lightning_client_mock.go index 37c4a9d..d93359c 100644 --- a/test/lightning_client_mock.go +++ b/test/lightning_client_mock.go @@ -193,6 +193,21 @@ func (h *mockLightningClient) ForwardingHistory(_ context.Context, }, nil } +// ListInvoices returns our mock's invoices. +func (h *mockLightningClient) ListInvoices(_ context.Context, + _ lndclient.ListInvoicesRequest) (*lndclient.ListInvoicesResponse, + error) { + + invoices := make([]lndclient.Invoice, 0, len(h.lnd.Invoices)) + for _, invoice := range h.lnd.Invoices { + invoices = append(invoices, *invoice) + } + + return &lndclient.ListInvoicesResponse{ + Invoices: invoices, + }, nil +} + // ChannelBackup retrieves the backup for a particular channel. The // backup is returned as an encrypted chanbackup.Single payload. func (h *mockLightningClient) ChannelBackup(context.Context, wire.OutPoint) ([]byte, error) { From 475c7f29f12f157d1946194b52dd02f539bfffec Mon Sep 17 00:00:00 2001 From: carla Date: Wed, 17 Jun 2020 08:44:43 +0200 Subject: [PATCH 5/7] lndclient: add listpayments endpoint --- lndclient/lightning_client.go | 119 ++++++++++++++++++++++++++++++++++ test/lightning_client_mock.go | 10 +++ test/lnd_services_mock.go | 1 + 3 files changed, 130 insertions(+) diff --git a/lndclient/lightning_client.go b/lndclient/lightning_client.go index 244a78d..de1c3e1 100644 --- a/lndclient/lightning_client.go +++ b/lndclient/lightning_client.go @@ -62,6 +62,10 @@ type LightningClient interface { ListInvoices(ctx context.Context, req ListInvoicesRequest) ( *ListInvoicesResponse, error) + // ListPayments makes a paginated call to our list payments endpoint. + ListPayments(ctx context.Context, + req ListPaymentsRequest) (*ListPaymentsResponse, error) + // ChannelBackup retrieves the backup for a particular channel. The // backup is returned as an encrypted chanbackup.Single payload. ChannelBackup(context.Context, wire.OutPoint) ([]byte, error) @@ -1022,6 +1026,121 @@ func (s *lightningClient) ListInvoices(ctx context.Context, }, nil } +// Payment represents a payment made by our node. +type Payment struct { + // Hash is the payment hash used. + Hash lntypes.Hash + + // Preimage is the preimage of the payment. It will have a non-nil value + // if the payment is settled. + Preimage *lntypes.Preimage + + // Amount is the amount in millisatoshis of the payment. + Amount lnwire.MilliSatoshi + + // Fee is the amount in millisatoshis that was paid in fees. + Fee lnwire.MilliSatoshi + + // Status describes the state of a payment. + Status *PaymentStatus + + // Htlcs is the set of htlc attempts made by the payment. + Htlcs []*lnrpc.HTLCAttempt + + // SequenceNumber is a unique id for each payment. + SequenceNumber uint64 +} + +// ListPaymentsRequest contains the request parameters for a paginated +// list payments call. +type ListPaymentsRequest struct { + // MaxPayments is the maximum number of payments to return. + MaxPayments uint64 + + // Offset is the index from which to start querying. + Offset uint64 + + // Reversed is set to query our payments backwards. + Reversed bool + + // IncludeIncomplete is set if we want to include incomplete payments. + IncludeIncomplete bool +} + +// ListPaymentsResponse contains the response to a list payments query, +// including the index offsets required for paginated queries. +type ListPaymentsResponse struct { + // FirstIndexOffset is the index offset of the first item in our set. + FirstIndexOffset uint64 + + // LastIndexOffset is the index offset of the last item in our set. + LastIndexOffset uint64 + + // Payments is the set of invoices that were returned. + Payments []Payment +} + +// ListPayments makes a paginated call to our listpayments endpoint. +func (s *lightningClient) ListPayments(ctx context.Context, + req ListPaymentsRequest) (*ListPaymentsResponse, error) { + + rpcCtx, cancel := context.WithTimeout(ctx, rpcTimeout) + defer cancel() + + resp, err := s.client.ListPayments( + s.adminMac.WithMacaroonAuth(rpcCtx), + &lnrpc.ListPaymentsRequest{ + IncludeIncomplete: req.IncludeIncomplete, + IndexOffset: req.Offset, + MaxPayments: req.MaxPayments, + Reversed: req.Reversed, + }) + if err != nil { + return nil, err + } + + payments := make([]Payment, len(resp.Payments)) + for i, payment := range resp.Payments { + hash, err := lntypes.MakeHashFromStr(payment.PaymentHash) + if err != nil { + return nil, err + } + + status, err := unmarshallPaymentStatus(payment) + if err != nil { + return nil, err + } + + pmt := Payment{ + Hash: hash, + Status: status, + Htlcs: payment.Htlcs, + Amount: lnwire.MilliSatoshi(payment.ValueMsat), + Fee: lnwire.MilliSatoshi(payment.FeeMsat), + SequenceNumber: payment.PaymentIndex, + } + + // Add our preimage if it is known. + if payment.PaymentPreimage != "" { + preimage, err := lntypes.MakePreimageFromStr( + payment.PaymentPreimage, + ) + if err != nil { + return nil, err + } + pmt.Preimage = &preimage + } + + payments[i] = pmt + } + + return &ListPaymentsResponse{ + FirstIndexOffset: resp.FirstIndexOffset, + LastIndexOffset: resp.LastIndexOffset, + Payments: payments, + }, nil +} + // ChannelBackup retrieves the backup for a particular channel. The backup is // returned as an encrypted chanbackup.Single payload. func (s *lightningClient) ChannelBackup(ctx context.Context, diff --git a/test/lightning_client_mock.go b/test/lightning_client_mock.go index d93359c..6b68819 100644 --- a/test/lightning_client_mock.go +++ b/test/lightning_client_mock.go @@ -208,6 +208,16 @@ func (h *mockLightningClient) ListInvoices(_ context.Context, }, nil } +// ListPayments makes a paginated call to our list payments endpoint. +func (h *mockLightningClient) ListPayments(_ context.Context, + _ lndclient.ListPaymentsRequest) (*lndclient.ListPaymentsResponse, + error) { + + return &lndclient.ListPaymentsResponse{ + Payments: h.lnd.Payments, + }, nil +} + // ChannelBackup retrieves the backup for a particular channel. The // backup is returned as an encrypted chanbackup.Single payload. func (h *mockLightningClient) ChannelBackup(context.Context, wire.OutPoint) ([]byte, error) { diff --git a/test/lnd_services_mock.go b/test/lnd_services_mock.go index cf08715..d2e83fd 100644 --- a/test/lnd_services_mock.go +++ b/test/lnd_services_mock.go @@ -166,6 +166,7 @@ type LndMockServices struct { Channels []lndclient.ChannelInfo ClosedChannels []lndclient.ClosedChannel ForwardingEvents []lndclient.ForwardingEvent + Payments []lndclient.Payment WaitForFinished func() From c76f2c4cf168e4d3f2a07c0443bdc06c02b21be3 Mon Sep 17 00:00:00 2001 From: carla Date: Wed, 17 Jun 2020 08:44:44 +0200 Subject: [PATCH 6/7] lndclient: update listTransactions to return full transaction --- lndclient/lightning_client.go | 48 +++++++++++++++++++++++++++++++---- test/lightning_client_mock.go | 3 ++- test/lnd_services_mock.go | 6 +++-- 3 files changed, 49 insertions(+), 8 deletions(-) diff --git a/lndclient/lightning_client.go b/lndclient/lightning_client.go index de1c3e1..61093f3 100644 --- a/lndclient/lightning_client.go +++ b/lndclient/lightning_client.go @@ -45,7 +45,7 @@ type LightningClient interface { // ListTransactions returns all known transactions of the backing lnd // node. - ListTransactions(ctx context.Context) ([]*wire.MsgTx, error) + ListTransactions(ctx context.Context) ([]Transaction, error) // ListChannels retrieves all channels of the backing lnd node. ListChannels(ctx context.Context) ([]ChannelInfo, error) @@ -257,6 +257,35 @@ func (c Initiator) String() string { } } +// Transaction represents an on chain transaction. +type Transaction struct { + // Tx is the on chain transaction. + Tx *wire.MsgTx + + // TxHash is the transaction hash string. + TxHash string + + // Timestamp is the timestamp our wallet has for the transaction. + Timestamp time.Time + + // Amount is the balance change that this transaction had on addresses + // controlled by our wallet. + Amount btcutil.Amount + + // Fee is the amount of fees our wallet committed to this transaction. + // Note that this field is not exhaustive, as it does not account for + // fees taken from inputs that that wallet doesn't know it owns (for + // example, the fees taken from our channel balance when we close a + // channel). + Fee btcutil.Amount + + // Confirmations is the number of confirmations the transaction has. + Confirmations int32 + + // Label is an optional label set for on chain transactions. + Label string +} + var ( // ErrMalformedServerResponse is returned when the swap and/or prepay // invoice is malformed. @@ -675,7 +704,7 @@ func unmarshalInvoice(resp *lnrpc.Invoice) (*Invoice, error) { } // ListTransactions returns all known transactions of the backing lnd node. -func (s *lightningClient) ListTransactions(ctx context.Context) ([]*wire.MsgTx, error) { +func (s *lightningClient) ListTransactions(ctx context.Context) ([]Transaction, error) { rpcCtx, cancel := context.WithTimeout(ctx, rpcTimeout) defer cancel() @@ -686,8 +715,8 @@ func (s *lightningClient) ListTransactions(ctx context.Context) ([]*wire.MsgTx, return nil, err } - txs := make([]*wire.MsgTx, 0, len(resp.Transactions)) - for _, respTx := range resp.Transactions { + txs := make([]Transaction, len(resp.Transactions)) + for i, respTx := range resp.Transactions { rawTx, err := hex.DecodeString(respTx.RawTxHex) if err != nil { return nil, err @@ -697,7 +726,16 @@ func (s *lightningClient) ListTransactions(ctx context.Context) ([]*wire.MsgTx, if err := tx.Deserialize(bytes.NewReader(rawTx)); err != nil { return nil, err } - txs = append(txs, &tx) + + txs[i] = Transaction{ + Tx: &tx, + TxHash: tx.TxHash().String(), + Timestamp: time.Unix(respTx.TimeStamp, 0), + Amount: btcutil.Amount(respTx.Amount), + Fee: btcutil.Amount(respTx.TotalFees), + Confirmations: respTx.NumConfirmations, + Label: respTx.Label, + } } return txs, nil diff --git a/test/lightning_client_mock.go b/test/lightning_client_mock.go index 6b68819..a6702e7 100644 --- a/test/lightning_client_mock.go +++ b/test/lightning_client_mock.go @@ -160,11 +160,12 @@ func (h *mockLightningClient) LookupInvoice(_ context.Context, // ListTransactions returns all known transactions of the backing lnd node. func (h *mockLightningClient) ListTransactions( - ctx context.Context) ([]*wire.MsgTx, error) { + _ context.Context) ([]lndclient.Transaction, error) { h.lnd.lock.Lock() txs := h.lnd.Transactions h.lnd.lock.Unlock() + return txs, nil } diff --git a/test/lnd_services_mock.go b/test/lnd_services_mock.go index d2e83fd..eef5c28 100644 --- a/test/lnd_services_mock.go +++ b/test/lnd_services_mock.go @@ -157,7 +157,7 @@ type LndMockServices struct { Signature []byte SignatureMsg string - Transactions []*wire.MsgTx + Transactions []lndclient.Transaction // Invoices is a set of invoices that have been created by the mock, // keyed by hash string. @@ -188,7 +188,9 @@ func (s *LndMockServices) NotifyHeight(height int32) error { // AddRelevantTx marks the given transaction as relevant. func (s *LndMockServices) AddTx(tx *wire.MsgTx) { s.lock.Lock() - s.Transactions = append(s.Transactions, tx.Copy()) + s.Transactions = append(s.Transactions, lndclient.Transaction{ + Tx: tx.Copy(), + }) s.lock.Unlock() } From fbb1a3b20439963b00de80db98f628ce243f3dbf Mon Sep 17 00:00:00 2001 From: carla Date: Wed, 17 Jun 2020 08:44:44 +0200 Subject: [PATCH 7/7] lndclient: add list sweeps to wallet cilent --- lndclient/walletkit_client.go | 28 ++++++++++++++++++++++++++++ test/lnd_services_mock.go | 1 + test/walletkit_mock.go | 5 +++++ 3 files changed, 34 insertions(+) diff --git a/lndclient/walletkit_client.go b/lndclient/walletkit_client.go index d46409d..99ff405 100644 --- a/lndclient/walletkit_client.go +++ b/lndclient/walletkit_client.go @@ -57,6 +57,11 @@ type WalletKitClient interface { EstimateFee(ctx context.Context, confTarget int32) (chainfee.SatPerKWeight, error) + + // ListSweeps returns a list of sweep transaction ids known to our node. + // Note that this function only looks up transaction ids, and does not + // query our wallet for the full set of transactions. + ListSweeps(ctx context.Context) ([]string, error) } type walletKitClient struct { @@ -319,3 +324,26 @@ func (m *walletKitClient) EstimateFee(ctx context.Context, confTarget int32) ( return chainfee.SatPerKWeight(resp.SatPerKw), nil } + +// ListSweeps returns a list of sweep transaction ids known to our node. +// Note that this function only looks up transaction ids (Verbose=false), and +// does not query our wallet for the full set of transactions. +func (m *walletKitClient) ListSweeps(ctx context.Context) ([]string, error) { + rpcCtx, cancel := context.WithTimeout(ctx, rpcTimeout) + defer cancel() + + resp, err := m.client.ListSweeps( + m.walletKitMac.WithMacaroonAuth(rpcCtx), + &walletrpc.ListSweepsRequest{ + Verbose: false, + }, + ) + if err != nil { + return nil, err + } + + // Since we have requested the abbreviated response from lnd, we can + // just get our response to a list of sweeps and return it. + sweeps := resp.GetTransactionIds() + return sweeps.TransactionIds, nil +} diff --git a/test/lnd_services_mock.go b/test/lnd_services_mock.go index eef5c28..1e313dd 100644 --- a/test/lnd_services_mock.go +++ b/test/lnd_services_mock.go @@ -158,6 +158,7 @@ type LndMockServices struct { SignatureMsg string Transactions []lndclient.Transaction + Sweeps []string // Invoices is a set of invoices that have been created by the mock, // keyed by hash string. diff --git a/test/walletkit_mock.go b/test/walletkit_mock.go index 769297a..fcd59c9 100644 --- a/test/walletkit_mock.go +++ b/test/walletkit_mock.go @@ -126,3 +126,8 @@ func (m *mockWalletKit) EstimateFee(ctx context.Context, confTarget int32) ( return feeEstimate, nil } + +// ListSweeps returns a list of the sweep transaction ids known to our node. +func (m *mockWalletKit) ListSweeps(_ context.Context) ([]string, error) { + return m.lnd.Sweeps, nil +}