github.com/decred/dcrlnd@v0.7.6/lnrpc/wtclientrpc/wtclient.go (about) 1 package wtclientrpc 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "net" 8 "strconv" 9 10 "github.com/decred/dcrd/dcrec/secp256k1/v4" 11 "github.com/decred/dcrlnd/lncfg" 12 "github.com/decred/dcrlnd/lnrpc" 13 "github.com/decred/dcrlnd/lnwire" 14 "github.com/decred/dcrlnd/watchtower" 15 "github.com/decred/dcrlnd/watchtower/wtclient" 16 "github.com/decred/dcrlnd/watchtower/wtdb" 17 "github.com/decred/dcrlnd/watchtower/wtpolicy" 18 "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" 19 "google.golang.org/grpc" 20 "gopkg.in/macaroon-bakery.v2/bakery" 21 ) 22 23 const ( 24 // subServerName is the name of the sub rpc server. We'll use this name 25 // to register ourselves, and we also require that the main 26 // SubServerConfigDispatcher instance recognizes it as the name of our 27 // RPC service. 28 subServerName = "WatchtowerClientRPC" 29 ) 30 31 var ( 32 // macPermissions maps RPC calls to the permissions they require. 33 // 34 // TODO(wilmer): create tower macaroon? 35 macPermissions = map[string][]bakery.Op{ 36 "/wtclientrpc.WatchtowerClient/AddTower": {{ 37 Entity: "offchain", 38 Action: "write", 39 }}, 40 "/wtclientrpc.WatchtowerClient/RemoveTower": {{ 41 Entity: "offchain", 42 Action: "write", 43 }}, 44 "/wtclientrpc.WatchtowerClient/ListTowers": {{ 45 Entity: "offchain", 46 Action: "read", 47 }}, 48 "/wtclientrpc.WatchtowerClient/GetTowerInfo": {{ 49 Entity: "offchain", 50 Action: "read", 51 }}, 52 "/wtclientrpc.WatchtowerClient/Stats": {{ 53 Entity: "offchain", 54 Action: "read", 55 }}, 56 "/wtclientrpc.WatchtowerClient/Policy": {{ 57 Entity: "offchain", 58 Action: "read", 59 }}, 60 } 61 62 // ErrWtclientNotActive signals that RPC calls cannot be processed 63 // because the watchtower client is not active. 64 ErrWtclientNotActive = errors.New("watchtower client not active") 65 ) 66 67 // ServerShell is a shell struct holding a reference to the actual sub-server. 68 // It is used to register the gRPC sub-server with the root server before we 69 // have the necessary dependencies to populate the actual sub-server. 70 type ServerShell struct { 71 WatchtowerClientServer 72 } 73 74 // WatchtowerClient is the RPC server we'll use to interact with the backing 75 // active watchtower client. 76 // 77 // TODO(wilmer): better name? 78 type WatchtowerClient struct { 79 cfg Config 80 } 81 82 // A compile time check to ensure that WatchtowerClient fully implements the 83 // WatchtowerClientWatchtowerClient gRPC service. 84 var _ WatchtowerClientServer = (*WatchtowerClient)(nil) 85 86 // New returns a new instance of the wtclientrpc WatchtowerClient sub-server. 87 // We also return the set of permissions for the macaroons that we may create 88 // within this method. If the macaroons we need aren't found in the filepath, 89 // then we'll create them on start up. If we're unable to locate, or create the 90 // macaroons we need, then we'll return with an error. 91 func New(cfg *Config) (*WatchtowerClient, lnrpc.MacaroonPerms, error) { 92 return &WatchtowerClient{*cfg}, macPermissions, nil 93 } 94 95 // Start launches any helper goroutines required for the WatchtowerClient to 96 // function. 97 // 98 // NOTE: This is part of the lnrpc.SubWatchtowerClient interface. 99 func (c *WatchtowerClient) Start() error { 100 return nil 101 } 102 103 // Stop signals any active goroutines for a graceful closure. 104 // 105 // NOTE: This is part of the lnrpc.SubServer interface. 106 func (c *WatchtowerClient) Stop() error { 107 return nil 108 } 109 110 // Name returns a unique string representation of the sub-server. This can be 111 // used to identify the sub-server and also de-duplicate them. 112 // 113 // NOTE: This is part of the lnrpc.SubServer interface. 114 func (c *WatchtowerClient) Name() string { 115 return subServerName 116 } 117 118 // RegisterWithRootServer will be called by the root gRPC server to direct a sub 119 // RPC server to register itself with the main gRPC root server. Until this is 120 // called, each sub-server won't be able to have requests routed towards it. 121 // 122 // NOTE: This is part of the lnrpc.GrpcHandler interface. 123 func (r *ServerShell) RegisterWithRootServer(grpcServer *grpc.Server) error { 124 // We make sure that we register it with the main gRPC server to ensure 125 // all our methods are routed properly. 126 RegisterWatchtowerClientServer(grpcServer, r) 127 128 return nil 129 } 130 131 // RegisterWithRestServer will be called by the root REST mux to direct a sub 132 // RPC server to register itself with the main REST mux server. Until this is 133 // called, each sub-server won't be able to have requests routed towards it. 134 // 135 // NOTE: This is part of the lnrpc.GrpcHandler interface. 136 func (r *ServerShell) RegisterWithRestServer(ctx context.Context, 137 mux *runtime.ServeMux, dest string, opts []grpc.DialOption) error { 138 139 // We make sure that we register it with the main REST server to ensure 140 // all our methods are routed properly. 141 err := RegisterWatchtowerClientHandlerFromEndpoint(ctx, mux, dest, opts) 142 if err != nil { 143 return err 144 } 145 146 return nil 147 } 148 149 // CreateSubServer populates the subserver's dependencies using the passed 150 // SubServerConfigDispatcher. This method should fully initialize the 151 // sub-server instance, making it ready for action. It returns the macaroon 152 // permissions that the sub-server wishes to pass on to the root server for all 153 // methods routed towards it. 154 // 155 // NOTE: This is part of the lnrpc.GrpcHandler interface. 156 func (r *ServerShell) CreateSubServer(configRegistry lnrpc.SubServerConfigDispatcher) ( 157 lnrpc.SubServer, lnrpc.MacaroonPerms, error) { 158 159 subServer, macPermissions, err := createNewSubServer(configRegistry) 160 if err != nil { 161 return nil, nil, err 162 } 163 164 r.WatchtowerClientServer = subServer 165 return subServer, macPermissions, nil 166 } 167 168 // isActive returns nil if the watchtower client is initialized so that we can 169 // process RPC requests. 170 func (c *WatchtowerClient) isActive() error { 171 if c.cfg.Active { 172 return nil 173 } 174 return ErrWtclientNotActive 175 } 176 177 // AddTower adds a new watchtower reachable at the given address and considers 178 // it for new sessions. If the watchtower already exists, then any new addresses 179 // included will be considered when dialing it for session negotiations and 180 // backups. 181 func (c *WatchtowerClient) AddTower(ctx context.Context, 182 req *AddTowerRequest) (*AddTowerResponse, error) { 183 184 if err := c.isActive(); err != nil { 185 return nil, err 186 } 187 188 pubKey, err := secp256k1.ParsePubKey(req.Pubkey) 189 if err != nil { 190 return nil, err 191 } 192 addr, err := lncfg.ParseAddressString( 193 req.Address, strconv.Itoa(watchtower.DefaultPeerPort), 194 c.cfg.Resolver, 195 ) 196 if err != nil { 197 return nil, fmt.Errorf("invalid address %v: %v", req.Address, err) 198 } 199 200 towerAddr := &lnwire.NetAddress{ 201 IdentityKey: pubKey, 202 Address: addr, 203 } 204 205 // TODO(conner): make atomic via multiplexed client 206 if err := c.cfg.Client.AddTower(towerAddr); err != nil { 207 return nil, err 208 } 209 if err := c.cfg.AnchorClient.AddTower(towerAddr); err != nil { 210 return nil, err 211 } 212 213 return &AddTowerResponse{}, nil 214 } 215 216 // RemoveTower removes a watchtower from being considered for future session 217 // negotiations and from being used for any subsequent backups until it's added 218 // again. If an address is provided, then this RPC only serves as a way of 219 // removing the address from the watchtower instead. 220 func (c *WatchtowerClient) RemoveTower(ctx context.Context, 221 req *RemoveTowerRequest) (*RemoveTowerResponse, error) { 222 223 if err := c.isActive(); err != nil { 224 return nil, err 225 } 226 227 pubKey, err := secp256k1.ParsePubKey(req.Pubkey) 228 if err != nil { 229 return nil, err 230 } 231 232 var addr net.Addr 233 if req.Address != "" { 234 addr, err = lncfg.ParseAddressString( 235 req.Address, strconv.Itoa(watchtower.DefaultPeerPort), 236 c.cfg.Resolver, 237 ) 238 if err != nil { 239 return nil, fmt.Errorf("unable to parse tower "+ 240 "address %v: %v", req.Address, err) 241 } 242 } 243 244 // TODO(conner): make atomic via multiplexed client 245 err = c.cfg.Client.RemoveTower(pubKey, addr) 246 if err != nil { 247 return nil, err 248 } 249 err = c.cfg.AnchorClient.RemoveTower(pubKey, addr) 250 if err != nil { 251 return nil, err 252 } 253 254 return &RemoveTowerResponse{}, nil 255 } 256 257 // ListTowers returns the list of watchtowers registered with the client. 258 func (c *WatchtowerClient) ListTowers(ctx context.Context, 259 req *ListTowersRequest) (*ListTowersResponse, error) { 260 261 if err := c.isActive(); err != nil { 262 return nil, err 263 } 264 265 anchorTowers, err := c.cfg.AnchorClient.RegisteredTowers() 266 if err != nil { 267 return nil, err 268 } 269 270 legacyTowers, err := c.cfg.Client.RegisteredTowers() 271 if err != nil { 272 return nil, err 273 } 274 275 // Filter duplicates. 276 towers := make(map[wtdb.TowerID]*wtclient.RegisteredTower) 277 for _, tower := range anchorTowers { 278 towers[tower.Tower.ID] = tower 279 } 280 for _, tower := range legacyTowers { 281 towers[tower.Tower.ID] = tower 282 } 283 284 rpcTowers := make([]*Tower, 0, len(towers)) 285 for _, tower := range towers { 286 rpcTower := marshallTower(tower, req.IncludeSessions) 287 rpcTowers = append(rpcTowers, rpcTower) 288 } 289 290 return &ListTowersResponse{Towers: rpcTowers}, nil 291 } 292 293 // GetTowerInfo retrieves information for a registered watchtower. 294 func (c *WatchtowerClient) GetTowerInfo(ctx context.Context, 295 req *GetTowerInfoRequest) (*Tower, error) { 296 297 if err := c.isActive(); err != nil { 298 return nil, err 299 } 300 301 pubKey, err := secp256k1.ParsePubKey(req.Pubkey) 302 if err != nil { 303 return nil, err 304 } 305 306 var tower *wtclient.RegisteredTower 307 tower, err = c.cfg.Client.LookupTower(pubKey) 308 if err == wtdb.ErrTowerNotFound { 309 tower, err = c.cfg.AnchorClient.LookupTower(pubKey) 310 } 311 if err != nil { 312 return nil, err 313 } 314 315 return marshallTower(tower, req.IncludeSessions), nil 316 } 317 318 // Stats returns the in-memory statistics of the client since startup. 319 func (c *WatchtowerClient) Stats(ctx context.Context, 320 req *StatsRequest) (*StatsResponse, error) { 321 322 if err := c.isActive(); err != nil { 323 return nil, err 324 } 325 326 clientStats := []wtclient.ClientStats{ 327 c.cfg.Client.Stats(), 328 c.cfg.AnchorClient.Stats(), 329 } 330 331 var stats wtclient.ClientStats 332 for i := range clientStats { 333 // Grab a reference to the slice index rather than copying bc 334 // ClientStats contains a lock which cannot be copied by value. 335 stat := &clientStats[i] 336 337 stats.NumTasksAccepted += stat.NumTasksAccepted 338 stats.NumTasksIneligible += stat.NumTasksIneligible 339 stats.NumTasksPending += stat.NumTasksPending 340 stats.NumSessionsAcquired += stat.NumSessionsAcquired 341 stats.NumSessionsExhausted += stat.NumSessionsExhausted 342 } 343 344 return &StatsResponse{ 345 NumBackups: uint32(stats.NumTasksAccepted), 346 NumFailedBackups: uint32(stats.NumTasksIneligible), 347 NumPendingBackups: uint32(stats.NumTasksPending), 348 NumSessionsAcquired: uint32(stats.NumSessionsAcquired), 349 NumSessionsExhausted: uint32(stats.NumSessionsExhausted), 350 }, nil 351 } 352 353 // Policy returns the active watchtower client policy configuration. 354 func (c *WatchtowerClient) Policy(ctx context.Context, 355 req *PolicyRequest) (*PolicyResponse, error) { 356 357 if err := c.isActive(); err != nil { 358 return nil, err 359 } 360 361 var policy wtpolicy.Policy 362 switch req.PolicyType { 363 case PolicyType_LEGACY: 364 policy = c.cfg.Client.Policy() 365 case PolicyType_ANCHOR: 366 policy = c.cfg.AnchorClient.Policy() 367 default: 368 return nil, fmt.Errorf("unknown policy type: %v", 369 req.PolicyType) 370 } 371 372 return &PolicyResponse{ 373 MaxUpdates: uint32(policy.MaxUpdates), 374 SweepAtomsPerByte: uint32(policy.SweepFeeRate / 1000), 375 }, nil 376 } 377 378 // marshallTower converts a client registered watchtower into its corresponding 379 // RPC type. 380 func marshallTower(tower *wtclient.RegisteredTower, includeSessions bool) *Tower { 381 rpcAddrs := make([]string, 0, len(tower.Addresses)) 382 for _, addr := range tower.Addresses { 383 rpcAddrs = append(rpcAddrs, addr.String()) 384 } 385 386 var rpcSessions []*TowerSession 387 if includeSessions { 388 rpcSessions = make([]*TowerSession, 0, len(tower.Sessions)) 389 for _, session := range tower.Sessions { 390 atomsPerByte := session.Policy.SweepFeeRate / 1000 391 rpcSessions = append(rpcSessions, &TowerSession{ 392 NumBackups: uint32(len(session.AckedUpdates)), 393 NumPendingBackups: uint32(len(session.CommittedUpdates)), 394 MaxBackups: uint32(session.Policy.MaxUpdates), 395 SweepAtomsPerByte: uint32(atomsPerByte), 396 }) 397 } 398 } 399 400 return &Tower{ 401 Pubkey: tower.IdentityKey.SerializeCompressed(), 402 Addresses: rpcAddrs, 403 ActiveSessionCandidate: tower.ActiveSessionCandidate, 404 NumSessions: uint32(len(tower.Sessions)), 405 Sessions: rpcSessions, 406 } 407 }