github.com/decred/dcrlnd@v0.7.6/lnrpc/invoicesrpc/invoices_server.go (about) 1 //go:build !no_invoicesrpc 2 // +build !no_invoicesrpc 3 4 package invoicesrpc 5 6 import ( 7 "context" 8 "fmt" 9 "io/ioutil" 10 "os" 11 "path/filepath" 12 13 "google.golang.org/grpc" 14 "google.golang.org/grpc/codes" 15 "google.golang.org/grpc/status" 16 "gopkg.in/macaroon-bakery.v2/bakery" 17 18 "github.com/decred/dcrlnd/channeldb" 19 "github.com/decred/dcrlnd/lnrpc" 20 "github.com/decred/dcrlnd/lntypes" 21 "github.com/decred/dcrlnd/macaroons" 22 "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" 23 ) 24 25 const ( 26 // subServerName is the name of the sub rpc server. We'll use this name 27 // to register ourselves, and we also require that the main 28 // SubServerConfigDispatcher instance recognize it as the name of our 29 // RPC service. 30 subServerName = "InvoicesRPC" 31 ) 32 33 var ( 34 // macaroonOps are the set of capabilities that our minted macaroon (if 35 // it doesn't already exist) will have. 36 macaroonOps = []bakery.Op{ 37 { 38 Entity: "invoices", 39 Action: "write", 40 }, 41 { 42 Entity: "invoices", 43 Action: "read", 44 }, 45 } 46 47 // macPermissions maps RPC calls to the permissions they require. 48 macPermissions = map[string][]bakery.Op{ 49 "/invoicesrpc.Invoices/SubscribeSingleInvoice": {{ 50 Entity: "invoices", 51 Action: "read", 52 }}, 53 "/invoicesrpc.Invoices/SettleInvoice": {{ 54 Entity: "invoices", 55 Action: "write", 56 }}, 57 "/invoicesrpc.Invoices/CancelInvoice": {{ 58 Entity: "invoices", 59 Action: "write", 60 }}, 61 "/invoicesrpc.Invoices/AddHoldInvoice": {{ 62 Entity: "invoices", 63 Action: "write", 64 }}, 65 "/invoicesrpc.Invoices/LookupInvoiceV2": {{ 66 Entity: "invoices", 67 Action: "write", 68 }}, 69 } 70 71 // DefaultInvoicesMacFilename is the default name of the invoices 72 // macaroon that we expect to find via a file handle within the main 73 // configuration file in this package. 74 DefaultInvoicesMacFilename = "invoices.macaroon" 75 ) 76 77 // ServerShell is a shell struct holding a reference to the actual sub-server. 78 // It is used to register the gRPC sub-server with the root server before we 79 // have the necessary dependencies to populate the actual sub-server. 80 type ServerShell struct { 81 InvoicesServer 82 } 83 84 // Server is a sub-server of the main RPC server: the invoices RPC. This sub 85 // RPC server allows external callers to access the status of the invoices 86 // currently active within lnd, as well as configuring it at runtime. 87 type Server struct { 88 quit chan struct{} 89 90 cfg *Config 91 } 92 93 // A compile time check to ensure that Server fully implements the 94 // InvoicesServer gRPC service. 95 var _ InvoicesServer = (*Server)(nil) 96 97 // New returns a new instance of the invoicesrpc Invoices sub-server. We also 98 // return the set of permissions for the macaroons that we may create within 99 // this method. If the macaroons we need aren't found in the filepath, then 100 // we'll create them on start up. If we're unable to locate, or create the 101 // macaroons we need, then we'll return with an error. 102 func New(cfg *Config) (*Server, lnrpc.MacaroonPerms, error) { 103 // If the path of the invoices macaroon wasn't specified, then we'll 104 // assume that it's found at the default network directory. 105 macFilePath := filepath.Join( 106 cfg.NetworkDir, DefaultInvoicesMacFilename, 107 ) 108 109 // Now that we know the full path of the invoices macaroon, we can 110 // check to see if we need to create it or not. If stateless_init is set 111 // then we don't write the macaroons. 112 if cfg.MacService != nil && !cfg.MacService.StatelessInit && 113 !lnrpc.FileExists(macFilePath) { 114 115 log.Infof("Baking macaroons for invoices RPC Server at: %v", 116 macFilePath) 117 118 // At this point, we know that the invoices macaroon doesn't 119 // yet, exist, so we need to create it with the help of the 120 // main macaroon service. 121 invoicesMac, err := cfg.MacService.NewMacaroon( 122 context.Background(), macaroons.DefaultRootKeyID, 123 macaroonOps..., 124 ) 125 if err != nil { 126 return nil, nil, err 127 } 128 invoicesMacBytes, err := invoicesMac.M().MarshalBinary() 129 if err != nil { 130 return nil, nil, err 131 } 132 err = ioutil.WriteFile(macFilePath, invoicesMacBytes, 0644) 133 if err != nil { 134 _ = os.Remove(macFilePath) 135 return nil, nil, err 136 } 137 } 138 139 server := &Server{ 140 cfg: cfg, 141 quit: make(chan struct{}, 1), 142 } 143 144 return server, macPermissions, nil 145 } 146 147 // Start launches any helper goroutines required for the Server to function. 148 // 149 // NOTE: This is part of the lnrpc.SubServer interface. 150 func (s *Server) Start() error { 151 return nil 152 } 153 154 // Stop signals any active goroutines for a graceful closure. 155 // 156 // NOTE: This is part of the lnrpc.SubServer interface. 157 func (s *Server) Stop() error { 158 close(s.quit) 159 160 return nil 161 } 162 163 // Name returns a unique string representation of the sub-server. This can be 164 // used to identify the sub-server and also de-duplicate them. 165 // 166 // NOTE: This is part of the lnrpc.SubServer interface. 167 func (s *Server) Name() string { 168 return subServerName 169 } 170 171 // RegisterWithRootServer will be called by the root gRPC server to direct a sub 172 // RPC server to register itself with the main gRPC root server. Until this is 173 // called, each sub-server won't be able to have requests routed towards it. 174 // 175 // NOTE: This is part of the lnrpc.GrpcHandler interface. 176 func (r *ServerShell) RegisterWithRootServer(grpcServer *grpc.Server) error { 177 // We make sure that we register it with the main gRPC server to ensure 178 // all our methods are routed properly. 179 RegisterInvoicesServer(grpcServer, r) 180 181 log.Debugf("Invoices RPC server successfully registered with root " + 182 "gRPC server") 183 184 return nil 185 } 186 187 // RegisterWithRestServer will be called by the root REST mux to direct a sub 188 // RPC server to register itself with the main REST mux server. Until this is 189 // called, each sub-server won't be able to have requests routed towards it. 190 // 191 // NOTE: This is part of the lnrpc.GrpcHandler interface. 192 func (r *ServerShell) RegisterWithRestServer(ctx context.Context, 193 mux *runtime.ServeMux, dest string, opts []grpc.DialOption) error { 194 195 // We make sure that we register it with the main REST server to ensure 196 // all our methods are routed properly. 197 err := RegisterInvoicesHandlerFromEndpoint(ctx, mux, dest, opts) 198 if err != nil { 199 log.Errorf("Could not register Invoices REST server "+ 200 "with root REST server: %v", err) 201 return err 202 } 203 204 log.Debugf("Invoices REST server successfully registered with " + 205 "root REST server") 206 return nil 207 } 208 209 // CreateSubServer populates the subserver's dependencies using the passed 210 // SubServerConfigDispatcher. This method should fully initialize the 211 // sub-server instance, making it ready for action. It returns the macaroon 212 // permissions that the sub-server wishes to pass on to the root server for all 213 // methods routed towards it. 214 // 215 // NOTE: This is part of the lnrpc.GrpcHandler interface. 216 func (r *ServerShell) CreateSubServer(configRegistry lnrpc.SubServerConfigDispatcher) ( 217 lnrpc.SubServer, lnrpc.MacaroonPerms, error) { 218 219 subServer, macPermissions, err := createNewSubServer(configRegistry) 220 if err != nil { 221 return nil, nil, err 222 } 223 224 r.InvoicesServer = subServer 225 return subServer, macPermissions, nil 226 } 227 228 // SubscribeSingleInvoice returns a uni-directional stream (server -> client) 229 // for notifying the client of state changes for a specified invoice. 230 func (s *Server) SubscribeSingleInvoice(req *SubscribeSingleInvoiceRequest, 231 updateStream Invoices_SubscribeSingleInvoiceServer) error { 232 233 hash, err := lntypes.MakeHash(req.RHash) 234 if err != nil { 235 return err 236 } 237 238 invoiceClient, err := s.cfg.InvoiceRegistry.SubscribeSingleInvoice(hash) 239 if err != nil { 240 return err 241 } 242 defer invoiceClient.Cancel() 243 244 for { 245 select { 246 case newInvoice := <-invoiceClient.Updates: 247 rpcInvoice, err := CreateRPCInvoice( 248 newInvoice, s.cfg.ChainParams, 249 ) 250 if err != nil { 251 return err 252 } 253 254 if err := updateStream.Send(rpcInvoice); err != nil { 255 return err 256 } 257 258 // If we have reached a terminal state, close the 259 // stream with no error. 260 if newInvoice.State.IsFinal() { 261 return nil 262 } 263 264 case <-updateStream.Context().Done(): 265 return updateStream.Context().Err() 266 267 case <-s.quit: 268 return nil 269 } 270 } 271 } 272 273 // SettleInvoice settles an accepted invoice. If the invoice is already settled, 274 // this call will succeed. 275 func (s *Server) SettleInvoice(ctx context.Context, 276 in *SettleInvoiceMsg) (*SettleInvoiceResp, error) { 277 278 preimage, err := lntypes.MakePreimage(in.Preimage) 279 if err != nil { 280 return nil, err 281 } 282 283 err = s.cfg.InvoiceRegistry.SettleHodlInvoice(preimage) 284 if err != nil && err != channeldb.ErrInvoiceAlreadySettled { 285 return nil, err 286 } 287 288 return &SettleInvoiceResp{}, nil 289 } 290 291 // CancelInvoice cancels a currently open invoice. If the invoice is already 292 // canceled, this call will succeed. If the invoice is already settled, it will 293 // fail. 294 func (s *Server) CancelInvoice(ctx context.Context, 295 in *CancelInvoiceMsg) (*CancelInvoiceResp, error) { 296 297 paymentHash, err := lntypes.MakeHash(in.PaymentHash) 298 if err != nil { 299 return nil, err 300 } 301 302 err = s.cfg.InvoiceRegistry.CancelInvoice(paymentHash) 303 if err != nil { 304 return nil, err 305 } 306 307 log.Infof("Canceled invoice %v", paymentHash) 308 309 return &CancelInvoiceResp{}, nil 310 } 311 312 // AddHoldInvoice attempts to add a new hold invoice to the invoice database. 313 // Any duplicated invoices are rejected, therefore all invoices *must* have a 314 // unique payment hash. 315 func (s *Server) AddHoldInvoice(ctx context.Context, 316 invoice *AddHoldInvoiceRequest) (*AddHoldInvoiceResp, error) { 317 318 addInvoiceCfg := &AddInvoiceConfig{ 319 AddInvoice: s.cfg.InvoiceRegistry.AddInvoice, 320 IsChannelActive: s.cfg.IsChannelActive, 321 ChainParams: s.cfg.ChainParams, 322 NodeSigner: s.cfg.NodeSigner, 323 DefaultCLTVExpiry: s.cfg.DefaultCLTVExpiry, 324 ChanDB: s.cfg.ChanStateDB, 325 Graph: s.cfg.GraphDB, 326 GenInvoiceFeatures: s.cfg.GenInvoiceFeatures, 327 GenAmpInvoiceFeatures: s.cfg.GenAmpInvoiceFeatures, 328 } 329 330 hash, err := lntypes.MakeHash(invoice.Hash) 331 if err != nil { 332 return nil, err 333 } 334 335 value, err := lnrpc.UnmarshallAmt(invoice.Value, invoice.ValueMAtoms) 336 if err != nil { 337 return nil, err 338 } 339 340 // Convert the passed routing hints to the required format. 341 routeHints, err := CreateZpay32HopHints(invoice.RouteHints) 342 if err != nil { 343 return nil, err 344 } 345 addInvoiceData := &AddInvoiceData{ 346 Memo: invoice.Memo, 347 Hash: &hash, 348 Value: value, 349 DescriptionHash: invoice.DescriptionHash, 350 Expiry: invoice.Expiry, 351 FallbackAddr: invoice.FallbackAddr, 352 CltvExpiry: invoice.CltvExpiry, 353 Private: invoice.Private, 354 HodlInvoice: true, 355 Preimage: nil, 356 RouteHints: routeHints, 357 } 358 359 _, dbInvoice, err := AddInvoice(ctx, addInvoiceCfg, addInvoiceData) 360 if err != nil { 361 return nil, err 362 } 363 364 return &AddHoldInvoiceResp{ 365 AddIndex: dbInvoice.AddIndex, 366 PaymentRequest: string(dbInvoice.PaymentRequest), 367 PaymentAddr: dbInvoice.Terms.PaymentAddr[:], 368 }, nil 369 } 370 371 // LookupInvoiceV2 attempts to look up at invoice. An invoice can be referenced 372 // using either its payment hash, payment address, or set ID. 373 func (s *Server) LookupInvoiceV2(ctx context.Context, 374 req *LookupInvoiceMsg) (*lnrpc.Invoice, error) { 375 376 var invoiceRef channeldb.InvoiceRef 377 378 // First, we'll attempt to parse out the invoice ref from the proto 379 // oneof. If none of the three currently supported types was 380 // specified, then we'll exit with an error. 381 switch { 382 case req.GetPaymentHash() != nil: 383 payHash, err := lntypes.MakeHash(req.GetPaymentHash()) 384 if err != nil { 385 return nil, status.Error( 386 codes.InvalidArgument, 387 fmt.Sprintf("unable to parse pay hash: %v", err), 388 ) 389 } 390 391 invoiceRef = channeldb.InvoiceRefByHash(payHash) 392 393 case req.GetPaymentAddr() != nil && 394 req.LookupModifier == LookupModifier_HTLC_SET_BLANK: 395 396 var payAddr [32]byte 397 copy(payAddr[:], req.GetPaymentAddr()) 398 399 invoiceRef = channeldb.InvoiceRefByAddrBlankHtlc(payAddr) 400 401 case req.GetPaymentAddr() != nil: 402 var payAddr [32]byte 403 copy(payAddr[:], req.GetPaymentAddr()) 404 405 invoiceRef = channeldb.InvoiceRefByAddr(payAddr) 406 407 case req.GetSetId() != nil && 408 req.LookupModifier == LookupModifier_HTLC_SET_ONLY: 409 410 var setID [32]byte 411 copy(setID[:], req.GetSetId()) 412 413 invoiceRef = channeldb.InvoiceRefBySetIDFiltered(setID) 414 415 case req.GetSetId() != nil: 416 var setID [32]byte 417 copy(setID[:], req.GetSetId()) 418 419 invoiceRef = channeldb.InvoiceRefBySetID(setID) 420 421 default: 422 return nil, status.Error(codes.InvalidArgument, 423 "invoice ref must be set") 424 } 425 426 // Attempt to locate the invoice, returning a nice "not found" error if 427 // we can't find it in the database. 428 invoice, err := s.cfg.InvoiceRegistry.LookupInvoiceByRef(invoiceRef) 429 switch { 430 case err == channeldb.ErrInvoiceNotFound: 431 return nil, status.Error(codes.NotFound, err.Error()) 432 case err != nil: 433 return nil, err 434 } 435 436 return CreateRPCInvoice(&invoice, s.cfg.ChainParams) 437 }