Skip to content

Commit 6d515e9

Browse files
authored
Merge pull request #235 from Jontified/master
Add WatchtowerClient interface
2 parents 349456a + f7e34ad commit 6d515e9

File tree

3 files changed

+245
-1
lines changed

3 files changed

+245
-1
lines changed

lnd_services.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,7 @@ type LndServices struct {
180180
Router RouterClient
181181
Versioner VersionerClient
182182
State StateClient
183+
WtClient WatchtowerClientClient
183184

184185
ChainParams *chaincfg.Params
185186
NodeAlias string
@@ -385,6 +386,9 @@ func NewLndServices(cfg *LndServicesConfig) (*GrpcLndServices, error) {
385386
routerClient := newRouterClient(
386387
conn, macaroons[RouterServiceMac], timeout,
387388
)
389+
wtClient := newWtClient(
390+
conn, macaroons[WalletKitServiceMac], timeout,
391+
)
388392

389393
cleanup := func() {
390394
log.Debugf("Closing lnd connection")
@@ -416,6 +420,7 @@ func NewLndServices(cfg *LndServicesConfig) (*GrpcLndServices, error) {
416420
Router: routerClient,
417421
Versioner: versionerClient,
418422
State: stateClient,
423+
WtClient: wtClient,
419424
ChainParams: chainParams,
420425
NodeAlias: nodeAlias,
421426
NodePubkey: route.Vertex(nodeKey),

macaroon_recipes.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ var (
2121
"signrpc": (*SignerClient)(nil),
2222
"verrpc": (*VersionerClient)(nil),
2323
"walletrpc": (*WalletKitClient)(nil),
24+
"wtclientrpc": (*WatchtowerClientClient)(nil),
2425
}
2526

2627
// renames is a map of renamed RPC method names. The key is the name as
@@ -77,7 +78,7 @@ func MacaroonRecipe(c LightningClient, packages []string) ([]MacaroonPermission,
7778
// From the pointer type we can find out the interface, its name
7879
// and what methods it declares.
7980
ifaceType := reflect.TypeOf(ifacePtr).Elem()
80-
serverName := strings.ReplaceAll(ifaceType.Name(), "Client", "")
81+
serverName := strings.TrimSuffix(ifaceType.Name(), "Client")
8182
for i := range ifaceType.NumMethod() {
8283
// The methods in lndclient might be called slightly
8384
// differently. Rename according to our rename mapping

wtclient_client.go

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

0 commit comments

Comments
 (0)