Skip to content

Commit ab4f76d

Browse files
committed
lndclient: Add watchtower client interface
Add the wtclient_client.go file which contains the lnd watchtower client interface.
1 parent fdb6c62 commit ab4f76d

File tree

1 file changed

+244
-0
lines changed

1 file changed

+244
-0
lines changed

wtclient_client.go

Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
package lndclient
2+
3+
import (
4+
"context"
5+
"time"
6+
7+
"github.com/lightningnetwork/lnd/lnrpc/wtclientrpc"
8+
"google.golang.org/grpc"
9+
)
10+
11+
// Implementation of the watchtower client interface
12+
// https://lightning.engineering/api-docs/category/watchtowerclient-service/
13+
type WatchtowerClientClient interface {
14+
ServiceClient[wtclientrpc.WatchtowerClientClient]
15+
16+
AddTower(ctx context.Context, pubkey []byte, address string) error
17+
18+
// Sets the given tower's status to inactive so that it's not considered
19+
// for session negotiation. Returns a human readable status string.
20+
DeactivateTower(ctx context.Context, pubkey []byte) (string, error)
21+
22+
GetTowerInfo(ctx context.Context, pubkey []byte, includeSessions,
23+
excludeExhaustedSessions bool) (*wtclientrpc.Tower, error)
24+
25+
ListTowers(ctx context.Context, includeSessions,
26+
excludeExhaustedSessions bool) ([]*wtclientrpc.Tower, error)
27+
28+
Policy(ctx context.Context, policyType wtclientrpc.PolicyType) (*PolicyResponse, error)
29+
30+
RemoveTower(ctx context.Context, pubkey []byte, address string) error
31+
32+
Stats(ctx context.Context) (*StatsResponse, error)
33+
34+
// Terminates the given session and marks it to not be used for backups
35+
// anymore. Returns a human readable status string.
36+
TerminateSession(ctx context.Context, sessionId []byte) (string, error)
37+
}
38+
39+
// Response returned by `Policy`
40+
// https://lightning.engineering/api-docs/api/lnd/watchtower-client/policy/#wtclientrpcpolicyresponse
41+
type PolicyResponse struct {
42+
maxUpdates uint32
43+
sweepSatPerVbyte uint32
44+
}
45+
46+
// Response returned by `Stats`
47+
// https://lightning.engineering/api-docs/api/lnd/watchtower-client/stats/#wtclientrpcstatsresponse
48+
type StatsResponse struct {
49+
numBackups uint32
50+
numPendingBackups uint32
51+
numFailedBackups uint32
52+
numSessionsAcquired uint32
53+
numSessionsExhausted uint32
54+
}
55+
56+
type wtClientClient struct {
57+
client wtclientrpc.WatchtowerClientClient
58+
wtClientMac serializedMacaroon
59+
timeout time.Duration
60+
}
61+
62+
// A compile time check to ensure that wtClientClient implements the
63+
// WtclientClient interface.
64+
var _ WatchtowerClientClient = (*wtClientClient)(nil)
65+
66+
// newClientClient creates a new watchtower client interface.
67+
func newWtClientClient(conn grpc.ClientConnInterface,
68+
wtClientMac serializedMacaroon, timeout time.Duration) *wtClientClient {
69+
70+
return &wtClientClient{
71+
client: wtclientrpc.NewWatchtowerClientClient(conn),
72+
wtClientMac: wtClientMac,
73+
timeout: timeout,
74+
}
75+
}
76+
77+
// RawClientWithMacAuth returns a context with the proper macaroon
78+
// authentication, the default RPC timeout, and the raw client.
79+
func (m *wtClientClient) RawClientWithMacAuth(
80+
parentCtx context.Context) (context.Context, time.Duration,
81+
wtclientrpc.WatchtowerClientClient) {
82+
83+
return m.wtClientMac.WithMacaroonAuth(parentCtx), m.timeout, m.client
84+
}
85+
86+
// AddTower adds a new watchtower reachable at the given address and considers
87+
// it for new sessions. If the watchtower already exists, then any new addresses
88+
// included will be considered when dialing it for session negotiations and
89+
// backups.
90+
func (m *wtClientClient) AddTower(ctx context.Context, pubkey []byte, address string) error {
91+
rpcCtx, cancel := context.WithTimeout(ctx, m.timeout)
92+
defer cancel()
93+
94+
rpcReq := &wtclientrpc.AddTowerRequest{
95+
Pubkey: pubkey,
96+
Address: address,
97+
}
98+
99+
rpcCtx = m.wtClientMac.WithMacaroonAuth(rpcCtx)
100+
_, err := m.client.AddTower(rpcCtx, rpcReq)
101+
if err != nil {
102+
return err
103+
}
104+
105+
return nil
106+
}
107+
108+
// DeactivateTower sets the given tower's status to inactive so that it is not
109+
// considered for session negotiation. Its sessions will also not be used while
110+
// the tower is inactive.
111+
func (m *wtClientClient) DeactivateTower(ctx context.Context, pubkey []byte) (string, error) {
112+
rpcCtx, cancel := context.WithTimeout(ctx, m.timeout)
113+
defer cancel()
114+
115+
rpcCtx = m.wtClientMac.WithMacaroonAuth(rpcCtx)
116+
resp, err := m.client.DeactivateTower(rpcCtx, &wtclientrpc.DeactivateTowerRequest{
117+
Pubkey: pubkey,
118+
})
119+
if err != nil {
120+
return "", err
121+
}
122+
123+
return resp.Status, nil
124+
}
125+
126+
// GetTowerInfo gets information about a watchtower that corresponds to the
127+
// given pubkey. The `includeSessions` flag controls whether session information is
128+
// included. The `excludeExhaustedSessions` controls whether exhausted sessions
129+
// are included in the response.
130+
func (m *wtClientClient) GetTowerInfo(ctx context.Context, pubkey []byte, includeSessions,
131+
excludeExhaustedSessions bool) (*wtclientrpc.Tower, error) {
132+
133+
rpcCtx, cancel := context.WithTimeout(ctx, m.timeout)
134+
defer cancel()
135+
136+
rpcCtx = m.wtClientMac.WithMacaroonAuth(rpcCtx)
137+
resp, err := m.client.GetTowerInfo(rpcCtx, &wtclientrpc.GetTowerInfoRequest{
138+
Pubkey: pubkey,
139+
IncludeSessions: includeSessions,
140+
ExcludeExhaustedSessions: excludeExhaustedSessions,
141+
})
142+
if err != nil {
143+
return nil, err
144+
}
145+
146+
return resp, nil
147+
}
148+
149+
// ListTowers gets information about all registered watchtowers. The
150+
// `includeSessions` and `excludeExhaustedSessions` flags serve the same function as
151+
// in the `GetTowerInfo` method
152+
func (m *wtClientClient) ListTowers(ctx context.Context, includeSessions,
153+
excludeExhaustedSessions bool) ([]*wtclientrpc.Tower, error) {
154+
155+
rpcCtx, cancel := context.WithTimeout(ctx, m.timeout)
156+
defer cancel()
157+
158+
rpcCtx = m.wtClientMac.WithMacaroonAuth(rpcCtx)
159+
resp, err := m.client.ListTowers(rpcCtx, &wtclientrpc.ListTowersRequest{
160+
IncludeSessions: includeSessions,
161+
ExcludeExhaustedSessions: excludeExhaustedSessions,
162+
})
163+
if err != nil {
164+
return nil, err
165+
}
166+
167+
return resp.Towers, nil
168+
}
169+
170+
// Policy returns the active watchtower client policy configuration.
171+
func (m *wtClientClient) Policy(ctx context.Context, policyType wtclientrpc.PolicyType) (*PolicyResponse, error) {
172+
rpcCtx, cancel := context.WithTimeout(ctx, m.timeout)
173+
defer cancel()
174+
175+
rpcCtx = m.wtClientMac.WithMacaroonAuth(rpcCtx)
176+
resp, err := m.client.Policy(rpcCtx, &wtclientrpc.PolicyRequest{
177+
PolicyType: policyType,
178+
})
179+
if err != nil {
180+
return nil, err
181+
}
182+
183+
return &PolicyResponse{
184+
maxUpdates: resp.MaxUpdates,
185+
sweepSatPerVbyte: resp.SweepSatPerVbyte,
186+
}, nil
187+
}
188+
189+
// RemoveTower removes a watchtower from being considered for future session
190+
// negotiations and from being used for any subsequent backups until it's added
191+
// again. If an address is provided, then this RPC only serves as a way of
192+
// removing the address from the watchtower instead.
193+
func (m *wtClientClient) RemoveTower(ctx context.Context, pubkey []byte, address string) error {
194+
rpcCtx, cancel := context.WithTimeout(ctx, m.timeout)
195+
defer cancel()
196+
197+
rpcCtx = m.wtClientMac.WithMacaroonAuth(rpcCtx)
198+
_, err := m.client.RemoveTower(rpcCtx, &wtclientrpc.RemoveTowerRequest{
199+
Pubkey: pubkey,
200+
Address: address,
201+
})
202+
if err != nil {
203+
return err
204+
}
205+
206+
return nil
207+
}
208+
209+
// Stats returns the in-memory statistics of the client since startup.
210+
func (m *wtClientClient) Stats(ctx context.Context) (*StatsResponse, error) {
211+
rpcCtx, cancel := context.WithTimeout(ctx, m.timeout)
212+
defer cancel()
213+
214+
rpcCtx = m.wtClientMac.WithMacaroonAuth(rpcCtx)
215+
resp, err := m.client.Stats(rpcCtx, &wtclientrpc.StatsRequest{})
216+
if err != nil {
217+
return nil, err
218+
}
219+
220+
return &StatsResponse{
221+
numBackups: resp.NumBackups,
222+
numPendingBackups: resp.NumPendingBackups,
223+
numFailedBackups: resp.NumFailedBackups,
224+
numSessionsAcquired: resp.NumSessionsAcquired,
225+
numSessionsExhausted: resp.NumSessionsExhausted,
226+
}, nil
227+
}
228+
229+
// Terminate terminates the given session and marks it as terminal so that it is
230+
// not used for backups anymore.
231+
func (m *wtClientClient) TerminateSession(ctx context.Context, sessionId []byte) (string, error) {
232+
rpcCtx, cancel := context.WithTimeout(ctx, m.timeout)
233+
defer cancel()
234+
235+
rpcCtx = m.wtClientMac.WithMacaroonAuth(rpcCtx)
236+
resp, err := m.client.TerminateSession(rpcCtx, &wtclientrpc.TerminateSessionRequest{
237+
SessionId: sessionId,
238+
})
239+
if err != nil {
240+
return "", err
241+
}
242+
243+
return resp.Status, nil
244+
}

0 commit comments

Comments
 (0)