Skip to content

Commit c1a9889

Browse files
committed
lndclient: add closed channels to lnd client
1 parent 11a04e6 commit c1a9889

File tree

3 files changed

+268
-1
lines changed

3 files changed

+268
-1
lines changed

lndclient/lightning_client.go

Lines changed: 259 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ type LightningClient interface {
5050
// ListChannels retrieves all channels of the backing lnd node.
5151
ListChannels(ctx context.Context) ([]ChannelInfo, error)
5252

53+
// ClosedChannels returns all closed channels of the backing lnd node.
54+
ClosedChannels(ctx context.Context) ([]ClosedChannel, error)
55+
5356
// ChannelBackup retrieves the backup for a particular channel. The
5457
// backup is returned as an encrypted chanbackup.Single payload.
5558
ChannelBackup(context.Context, wire.OutPoint) ([]byte, error)
@@ -109,6 +112,138 @@ type ChannelInfo struct {
109112
Uptime time.Duration
110113
}
111114

115+
// ClosedChannel represents a channel that has been closed.
116+
type ClosedChannel struct {
117+
// ChannelPoint is the funding outpoint of the channel.
118+
ChannelPoint string
119+
120+
// ChannelID holds the unique channel ID for the channel. The first 3
121+
// bytes are the block height, the next 3 the index within the block,
122+
// and the last 2 bytes are the output index for the channel.
123+
ChannelID uint64
124+
125+
// ClosingTxHash is the tx hash of the close transaction for the channel.
126+
ClosingTxHash string
127+
128+
// CloseType is the type of channel closure.
129+
CloseType CloseType
130+
131+
// OpenInitiator is true if we opened the channel. This value is not
132+
// always available (older channels do not have it).
133+
OpenInitiator Initiator
134+
135+
// Initiator indicates which party initiated the channel close. Since
136+
// this value is not always set in the rpc response, we also make a best
137+
// effort attempt to set it based on CloseType.
138+
CloseInitiator Initiator
139+
140+
// PubKeyBytes is the raw bytes of the public key of the remote node.
141+
PubKeyBytes route.Vertex
142+
143+
// Capacity is the total amount of funds held in this channel.
144+
Capacity btcutil.Amount
145+
146+
// SettledBalance is the amount we were paid out directly in this
147+
// channel close. Note that this does not include cases where we need to
148+
// sweep our commitment or htlcs.
149+
SettledBalance btcutil.Amount
150+
}
151+
152+
// CloseType is an enum which represents the types of closes our channels may
153+
// have. This type maps to the rpc value.
154+
type CloseType uint8
155+
156+
const (
157+
// CloseTypeCooperative represents cooperative closes.
158+
CloseTypeCooperative CloseType = iota
159+
160+
// CloseTypeLocalForce represents force closes that we initiated.
161+
CloseTypeLocalForce
162+
163+
// CloseTypeRemoteForce represents force closes that our peer initiated.
164+
CloseTypeRemoteForce
165+
166+
// CloseTypeBreach represents breach closes from our peer.
167+
CloseTypeBreach
168+
169+
// CloseTypeFundingCancelled represents channels which were never
170+
// created because their funding transaction was cancelled.
171+
CloseTypeFundingCancelled
172+
173+
// CloseTypeAbandoned represents a channel that was abandoned.
174+
CloseTypeAbandoned
175+
)
176+
177+
// String returns the string representation of a close type.
178+
func (c CloseType) String() string {
179+
switch c {
180+
case CloseTypeCooperative:
181+
return "Cooperative"
182+
183+
case CloseTypeLocalForce:
184+
return "Local Force"
185+
186+
case CloseTypeRemoteForce:
187+
return "Remote Force"
188+
189+
case CloseTypeBreach:
190+
return "Breach"
191+
192+
case CloseTypeFundingCancelled:
193+
return "Funding Cancelled"
194+
195+
case CloseTypeAbandoned:
196+
return "Abandoned"
197+
198+
default:
199+
return "Unknown"
200+
}
201+
}
202+
203+
// Initiator indicates the party that opened or closed a channel. This enum is
204+
// used for cases where we may not have a full set of initiator information
205+
// available over rpc (this is the case for older channels).
206+
type Initiator uint8
207+
208+
const (
209+
// InitiatorUnrecorded is set when we do not know the open/close
210+
// initiator for a channel, this is the case when the channel was
211+
// closed before lnd started tracking initiators.
212+
InitiatorUnrecorded Initiator = iota
213+
214+
// InitiatorLocal is set when we initiated a channel open or close.
215+
InitiatorLocal
216+
217+
// InitiatorRemote is set when the remote party initiated a chanel open
218+
// or close.
219+
InitiatorRemote
220+
221+
// InitiatorBoth is set in the case where both parties initiated a
222+
// cooperative close (this is possible with multiple rounds of
223+
// negotiation).
224+
InitiatorBoth
225+
)
226+
227+
// String provides the string represenetation of a close initiator.
228+
func (c Initiator) String() string {
229+
switch c {
230+
case InitiatorUnrecorded:
231+
return "Unrecorded"
232+
233+
case InitiatorLocal:
234+
return "Local"
235+
236+
case InitiatorRemote:
237+
return "Remote"
238+
239+
case InitiatorBoth:
240+
return "Both"
241+
242+
default:
243+
return fmt.Sprintf("unknown initiator: %d", c)
244+
}
245+
}
246+
112247
var (
113248
// ErrMalformedServerResponse is returned when the swap and/or prepay
114249
// invoice is malformed.
@@ -583,6 +718,130 @@ func (s *lightningClient) ListChannels(ctx context.Context) (
583718
return result, nil
584719
}
585720

721+
// ClosedChannels returns a list of our closed channels.
722+
func (s *lightningClient) ClosedChannels(ctx context.Context) ([]ClosedChannel,
723+
error) {
724+
725+
rpcCtx, cancel := context.WithTimeout(ctx, rpcTimeout)
726+
defer cancel()
727+
728+
response, err := s.client.ClosedChannels(
729+
s.adminMac.WithMacaroonAuth(rpcCtx),
730+
&lnrpc.ClosedChannelsRequest{},
731+
)
732+
if err != nil {
733+
return nil, err
734+
}
735+
736+
channels := make([]ClosedChannel, len(response.Channels))
737+
for i, channel := range response.Channels {
738+
remote, err := route.NewVertexFromStr(channel.RemotePubkey)
739+
if err != nil {
740+
return nil, err
741+
}
742+
743+
closeType, err := rpcCloseType(channel.CloseType)
744+
if err != nil {
745+
return nil, err
746+
}
747+
748+
openInitiator, err := getInitiator(channel.OpenInitiator)
749+
if err != nil {
750+
return nil, err
751+
}
752+
753+
closeInitiator, err := rpcCloseInitiator(
754+
channel.CloseInitiator, closeType,
755+
)
756+
if err != nil {
757+
return nil, err
758+
}
759+
760+
channels[i] = ClosedChannel{
761+
ChannelPoint: channel.ChannelPoint,
762+
ChannelID: channel.ChanId,
763+
ClosingTxHash: channel.ClosingTxHash,
764+
CloseType: closeType,
765+
OpenInitiator: openInitiator,
766+
CloseInitiator: closeInitiator,
767+
PubKeyBytes: remote,
768+
Capacity: btcutil.Amount(channel.Capacity),
769+
SettledBalance: btcutil.Amount(channel.SettledBalance),
770+
}
771+
}
772+
773+
return channels, nil
774+
}
775+
776+
// rpcCloseType maps a rpc close type to our local enum.
777+
func rpcCloseType(t lnrpc.ChannelCloseSummary_ClosureType) (CloseType, error) {
778+
switch t {
779+
case lnrpc.ChannelCloseSummary_COOPERATIVE_CLOSE:
780+
return CloseTypeCooperative, nil
781+
782+
case lnrpc.ChannelCloseSummary_LOCAL_FORCE_CLOSE:
783+
return CloseTypeLocalForce, nil
784+
785+
case lnrpc.ChannelCloseSummary_REMOTE_FORCE_CLOSE:
786+
return CloseTypeRemoteForce, nil
787+
788+
case lnrpc.ChannelCloseSummary_BREACH_CLOSE:
789+
return CloseTypeBreach, nil
790+
791+
case lnrpc.ChannelCloseSummary_FUNDING_CANCELED:
792+
return CloseTypeFundingCancelled, nil
793+
794+
case lnrpc.ChannelCloseSummary_ABANDONED:
795+
return CloseTypeAbandoned, nil
796+
797+
default:
798+
return 0, fmt.Errorf("unknown close type: %v", t)
799+
}
800+
}
801+
802+
// rpcCloseInitiator maps a close initiator to our local type. Since this field
803+
// is not always set in lnd for older channels, also use our close type to infer
804+
// who initiated the close when we have force closes.
805+
func rpcCloseInitiator(initiator lnrpc.Initiator,
806+
closeType CloseType) (Initiator, error) {
807+
808+
// Since our close type is always set on the rpc, we first check whether
809+
// we can figure out the close initiator from this value. This is only
810+
// possible for force closes/breaches.
811+
switch closeType {
812+
case CloseTypeLocalForce:
813+
return InitiatorLocal, nil
814+
815+
case CloseTypeRemoteForce, CloseTypeBreach:
816+
return InitiatorRemote, nil
817+
}
818+
819+
// Otherwise, we check whether our initiator field is set, and fail only
820+
// if we have an unknown type.
821+
return getInitiator(initiator)
822+
}
823+
824+
// getInitiator maps a rpc initiator value to our initiator enum.
825+
func getInitiator(initiator lnrpc.Initiator) (Initiator, error) {
826+
switch initiator {
827+
case lnrpc.Initiator_INITIATOR_LOCAL:
828+
return InitiatorLocal, nil
829+
830+
case lnrpc.Initiator_INITIATOR_REMOTE:
831+
return InitiatorRemote, nil
832+
833+
case lnrpc.Initiator_INITIATOR_BOTH:
834+
return InitiatorBoth, nil
835+
836+
case lnrpc.Initiator_INITIATOR_UNKNOWN:
837+
return InitiatorUnrecorded, nil
838+
839+
default:
840+
return InitiatorUnrecorded, fmt.Errorf("unknown "+
841+
"initiator: %v", initiator)
842+
}
843+
}
844+
586845
// ChannelBackup retrieves the backup for a particular channel. The backup is
587846
// returned as an encrypted chanbackup.Single payload.
588847
func (s *lightningClient) ChannelBackup(ctx context.Context,

test/lightning_client_mock.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,13 @@ func (h *mockLightningClient) ListChannels(ctx context.Context) (
175175
return h.lnd.Channels, nil
176176
}
177177

178+
// ClosedChannels returns a list of our closed channels.
179+
func (h *mockLightningClient) ClosedChannels(_ context.Context) ([]lndclient.ClosedChannel,
180+
error) {
181+
182+
return h.lnd.ClosedChannels, nil
183+
}
184+
178185
// ChannelBackup retrieves the backup for a particular channel. The
179186
// backup is returned as an encrypted chanbackup.Single payload.
180187
func (h *mockLightningClient) ChannelBackup(context.Context, wire.OutPoint) ([]byte, error) {

test/lnd_services_mock.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,8 @@ type LndMockServices struct {
163163
// keyed by hash string.
164164
Invoices map[lntypes.Hash]*lndclient.Invoice
165165

166-
Channels []lndclient.ChannelInfo
166+
Channels []lndclient.ChannelInfo
167+
ClosedChannels []lndclient.ClosedChannel
167168

168169
WaitForFinished func()
169170

0 commit comments

Comments
 (0)