github.com/decred/dcrlnd@v0.7.6/lnrpc/chainrpc/chainnotifier_server.go (about) 1 //go:build !no_chainrpc 2 // +build !no_chainrpc 3 4 package chainrpc 5 6 import ( 7 "bytes" 8 "context" 9 "errors" 10 "io/ioutil" 11 "os" 12 "path/filepath" 13 "sync" 14 15 "github.com/decred/dcrd/chaincfg/chainhash" 16 "github.com/decred/dcrd/wire" 17 "github.com/decred/dcrlnd/chainntnfs" 18 "github.com/decred/dcrlnd/lnrpc" 19 "github.com/decred/dcrlnd/macaroons" 20 "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" 21 "google.golang.org/grpc" 22 "gopkg.in/macaroon-bakery.v2/bakery" 23 ) 24 25 const ( 26 // subServerName is the name of the RPC sub-server. We'll use this name 27 // to register ourselves, and we also require that the main 28 // SubServerConfigDispatcher instance recognize this as the name of the 29 // config file that we need. 30 subServerName = "ChainRPC" 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: "onchain", 39 Action: "read", 40 }, 41 } 42 43 // macPermissions maps RPC calls to the permissions they require. 44 macPermissions = map[string][]bakery.Op{ 45 "/chainrpc.ChainNotifier/RegisterConfirmationsNtfn": {{ 46 Entity: "onchain", 47 Action: "read", 48 }}, 49 "/chainrpc.ChainNotifier/RegisterSpendNtfn": {{ 50 Entity: "onchain", 51 Action: "read", 52 }}, 53 "/chainrpc.ChainNotifier/RegisterBlockEpochNtfn": {{ 54 Entity: "onchain", 55 Action: "read", 56 }}, 57 } 58 59 // DefaultChainNotifierMacFilename is the default name of the chain 60 // notifier macaroon that we expect to find via a file handle within the 61 // main configuration file in this package. 62 DefaultChainNotifierMacFilename = "chainnotifier.macaroon" 63 64 // ErrChainNotifierServerShuttingDown is an error returned when we are 65 // waiting for a notification to arrive but the chain notifier server 66 // has been shut down. 67 ErrChainNotifierServerShuttingDown = errors.New("chain notifier RPC " + 68 "subserver shutting down") 69 70 // ErrChainNotifierServerNotActive indicates that the chain notifier hasn't 71 // finished the startup process. 72 ErrChainNotifierServerNotActive = errors.New("chain notifier RPC is " + 73 "still in the process of starting") 74 ) 75 76 // ServerShell is a shell struct holding a reference to the actual sub-server. 77 // It is used to register the gRPC sub-server with the root server before we 78 // have the necessary dependencies to populate the actual sub-server. 79 type ServerShell struct { 80 ChainNotifierServer 81 } 82 83 // Server is a sub-server of the main RPC server: the chain notifier RPC. This 84 // RPC sub-server allows external callers to access the full chain notifier 85 // capabilities of lnd. This allows callers to create custom protocols, external 86 // to lnd, even backed by multiple distinct lnd across independent failure 87 // domains. 88 type Server struct { 89 started sync.Once 90 stopped sync.Once 91 92 cfg Config 93 94 quit chan struct{} 95 } 96 97 // New returns a new instance of the chainrpc ChainNotifier 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 chain notifier macaroon wasn't generated, then 104 // we'll assume that it's found at the default network directory. 105 if cfg.ChainNotifierMacPath == "" { 106 cfg.ChainNotifierMacPath = filepath.Join( 107 cfg.NetworkDir, DefaultChainNotifierMacFilename, 108 ) 109 } 110 111 // Now that we know the full path of the chain notifier macaroon, we can 112 // check to see if we need to create it or not. If stateless_init is set 113 // then we don't write the macaroons. 114 macFilePath := cfg.ChainNotifierMacPath 115 if cfg.MacService != nil && !cfg.MacService.StatelessInit && 116 !lnrpc.FileExists(macFilePath) { 117 118 log.Infof("Baking macaroons for ChainNotifier RPC Server at: %v", 119 macFilePath) 120 121 // At this point, we know that the chain notifier macaroon 122 // doesn't yet, exist, so we need to create it with the help of 123 // the main macaroon service. 124 chainNotifierMac, err := cfg.MacService.NewMacaroon( 125 context.Background(), macaroons.DefaultRootKeyID, 126 macaroonOps..., 127 ) 128 if err != nil { 129 return nil, nil, err 130 } 131 chainNotifierMacBytes, err := chainNotifierMac.M().MarshalBinary() 132 if err != nil { 133 return nil, nil, err 134 } 135 err = ioutil.WriteFile(macFilePath, chainNotifierMacBytes, 0644) 136 if err != nil { 137 _ = os.Remove(macFilePath) 138 return nil, nil, err 139 } 140 } 141 142 return &Server{ 143 cfg: *cfg, 144 quit: make(chan struct{}), 145 }, macPermissions, nil 146 } 147 148 // Compile-time checks to ensure that Server fully implements the 149 // ChainNotifierServer gRPC service and lnrpc.SubServer interface. 150 var _ ChainNotifierServer = (*Server)(nil) 151 var _ lnrpc.SubServer = (*Server)(nil) 152 153 // Start launches any helper goroutines required for the server to function. 154 // 155 // NOTE: This is part of the lnrpc.SubServer interface. 156 func (s *Server) Start() error { 157 s.started.Do(func() {}) 158 return nil 159 } 160 161 // Stop signals any active goroutines for a graceful closure. 162 // 163 // NOTE: This is part of the lnrpc.SubServer interface. 164 func (s *Server) Stop() error { 165 s.stopped.Do(func() { 166 close(s.quit) 167 }) 168 return nil 169 } 170 171 // Name returns a unique string representation of the sub-server. This can be 172 // used to identify the sub-server and also de-duplicate them. 173 // 174 // NOTE: This is part of the lnrpc.SubServer interface. 175 func (s *Server) Name() string { 176 return subServerName 177 } 178 179 // RegisterWithRootServer will be called by the root gRPC server to direct a RPC 180 // sub-server to register itself with the main gRPC root server. Until this is 181 // called, each sub-server won't be able to have requests routed towards it. 182 // 183 // NOTE: This is part of the lnrpc.GrpcHandler interface. 184 func (r *ServerShell) RegisterWithRootServer(grpcServer *grpc.Server) error { 185 // We make sure that we register it with the main gRPC server to ensure 186 // all our methods are routed properly. 187 RegisterChainNotifierServer(grpcServer, r) 188 189 log.Debug("ChainNotifier RPC server successfully register with root " + 190 "gRPC server") 191 192 return nil 193 } 194 195 // RegisterWithRestServer will be called by the root REST mux to direct a sub 196 // RPC server to register itself with the main REST mux server. Until this is 197 // called, each sub-server won't be able to have requests routed towards it. 198 // 199 // NOTE: This is part of the lnrpc.GrpcHandler interface. 200 func (r *ServerShell) RegisterWithRestServer(ctx context.Context, 201 mux *runtime.ServeMux, dest string, opts []grpc.DialOption) error { 202 203 // We make sure that we register it with the main REST server to ensure 204 // all our methods are routed properly. 205 err := RegisterChainNotifierHandlerFromEndpoint(ctx, mux, dest, opts) 206 if err != nil { 207 log.Errorf("Could not register ChainNotifier REST server "+ 208 "with root REST server: %v", err) 209 return err 210 } 211 212 log.Debugf("ChainNotifier REST server successfully registered with " + 213 "root REST server") 214 return nil 215 } 216 217 // CreateSubServer populates the subserver's dependencies using the passed 218 // SubServerConfigDispatcher. This method should fully initialize the 219 // sub-server instance, making it ready for action. It returns the macaroon 220 // permissions that the sub-server wishes to pass on to the root server for all 221 // methods routed towards it. 222 // 223 // NOTE: This is part of the lnrpc.GrpcHandler interface. 224 func (r *ServerShell) CreateSubServer(configRegistry lnrpc.SubServerConfigDispatcher) ( 225 lnrpc.SubServer, lnrpc.MacaroonPerms, error) { 226 227 subServer, macPermissions, err := createNewSubServer(configRegistry) 228 if err != nil { 229 return nil, nil, err 230 } 231 232 r.ChainNotifierServer = subServer 233 return subServer, macPermissions, nil 234 } 235 236 // RegisterConfirmationsNtfn is a synchronous response-streaming RPC that 237 // registers an intent for a client to be notified once a confirmation request 238 // has reached its required number of confirmations on-chain. 239 // 240 // A client can specify whether the confirmation request should be for a 241 // particular transaction by its hash or for an output script by specifying a 242 // zero hash. 243 // 244 // NOTE: This is part of the chainrpc.ChainNotifierService interface. 245 func (s *Server) RegisterConfirmationsNtfn(in *ConfRequest, 246 confStream ChainNotifier_RegisterConfirmationsNtfnServer) error { 247 248 if !s.cfg.ChainNotifier.Started() { 249 return ErrChainNotifierServerNotActive 250 } 251 252 // We'll start by reconstructing the RPC request into what the 253 // underlying ChainNotifier expects. 254 var txid chainhash.Hash 255 copy(txid[:], in.Txid) 256 257 // We'll then register for the spend notification of the request. 258 confEvent, err := s.cfg.ChainNotifier.RegisterConfirmationsNtfn( 259 &txid, in.Script, in.NumConfs, in.HeightHint, 260 ) 261 if err != nil { 262 return err 263 } 264 defer confEvent.Cancel() 265 266 // With the request registered, we'll wait for its spend notification to 267 // be dispatched. 268 for { 269 select { 270 // The transaction satisfying the request has confirmed on-chain 271 // and reached its required number of confirmations. We'll 272 // dispatch an event to the caller indicating so. 273 case details, ok := <-confEvent.Confirmed: 274 if !ok { 275 return chainntnfs.ErrChainNotifierShuttingDown 276 } 277 278 var rawTxBuf bytes.Buffer 279 err := details.Tx.Serialize(&rawTxBuf) 280 if err != nil { 281 return err 282 } 283 284 rpcConfDetails := &ConfDetails{ 285 RawTx: rawTxBuf.Bytes(), 286 BlockHash: details.BlockHash[:], 287 BlockHeight: details.BlockHeight, 288 TxIndex: details.TxIndex, 289 } 290 291 conf := &ConfEvent{ 292 Event: &ConfEvent_Conf{ 293 Conf: rpcConfDetails, 294 }, 295 } 296 if err := confStream.Send(conf); err != nil { 297 return err 298 } 299 300 // The transaction satisfying the request has been reorged out 301 // of the chain, so we'll send an event describing it. 302 case _, ok := <-confEvent.NegativeConf: 303 if !ok { 304 return chainntnfs.ErrChainNotifierShuttingDown 305 } 306 307 reorg := &ConfEvent{ 308 Event: &ConfEvent_Reorg{Reorg: &Reorg{}}, 309 } 310 if err := confStream.Send(reorg); err != nil { 311 return err 312 } 313 314 // The transaction satisfying the request has confirmed and is 315 // no longer under the risk of being reorged out of the chain, 316 // so we can safely exit. 317 case _, ok := <-confEvent.Done: 318 if !ok { 319 return chainntnfs.ErrChainNotifierShuttingDown 320 } 321 322 return nil 323 324 // The response stream's context for whatever reason has been 325 // closed. If context is closed by an exceeded deadline we will 326 // return an error. 327 case <-confStream.Context().Done(): 328 if errors.Is(confStream.Context().Err(), context.Canceled) { 329 return nil 330 } 331 return confStream.Context().Err() 332 333 // The server has been requested to shut down. 334 case <-s.quit: 335 return ErrChainNotifierServerShuttingDown 336 } 337 } 338 } 339 340 // RegisterSpendNtfn is a synchronous response-streaming RPC that registers an 341 // intent for a client to be notification once a spend request has been spent by 342 // a transaction that has confirmed on-chain. 343 // 344 // A client can specify whether the spend request should be for a particular 345 // outpoint or for an output script by specifying a zero outpoint. 346 // 347 // NOTE: This is part of the chainrpc.ChainNotifierService interface. 348 func (s *Server) RegisterSpendNtfn(in *SpendRequest, 349 spendStream ChainNotifier_RegisterSpendNtfnServer) error { 350 351 if !s.cfg.ChainNotifier.Started() { 352 return ErrChainNotifierServerNotActive 353 } 354 355 // We'll start by reconstructing the RPC request into what the 356 // underlying ChainNotifier expects. 357 var op *wire.OutPoint 358 if in.Outpoint != nil { 359 var txid chainhash.Hash 360 copy(txid[:], in.Outpoint.Hash) 361 op = &wire.OutPoint{Hash: txid, Index: in.Outpoint.Index} 362 } 363 364 // We'll then register for the spend notification of the request. 365 spendEvent, err := s.cfg.ChainNotifier.RegisterSpendNtfn( 366 op, in.Script, in.HeightHint, 367 ) 368 if err != nil { 369 return err 370 } 371 defer spendEvent.Cancel() 372 373 // With the request registered, we'll wait for its spend notification to 374 // be dispatched. 375 for { 376 select { 377 // A transaction that spends the given has confirmed on-chain. 378 // We'll return an event to the caller indicating so that 379 // includes the details of the spending transaction. 380 case details, ok := <-spendEvent.Spend: 381 if !ok { 382 return chainntnfs.ErrChainNotifierShuttingDown 383 } 384 385 var rawSpendingTxBuf bytes.Buffer 386 err := details.SpendingTx.Serialize(&rawSpendingTxBuf) 387 if err != nil { 388 return err 389 } 390 391 rpcSpendDetails := &SpendDetails{ 392 SpendingOutpoint: &Outpoint{ 393 Hash: details.SpentOutPoint.Hash[:], 394 Index: details.SpentOutPoint.Index, 395 }, 396 RawSpendingTx: rawSpendingTxBuf.Bytes(), 397 SpendingTxHash: details.SpenderTxHash[:], 398 SpendingInputIndex: details.SpenderInputIndex, 399 SpendingHeight: uint32(details.SpendingHeight), 400 } 401 402 spend := &SpendEvent{ 403 Event: &SpendEvent_Spend{ 404 Spend: rpcSpendDetails, 405 }, 406 } 407 if err := spendStream.Send(spend); err != nil { 408 return err 409 } 410 411 // The spending transaction of the request has been reorged of 412 // the chain. We'll return an event to the caller indicating so. 413 case _, ok := <-spendEvent.Reorg: 414 if !ok { 415 return chainntnfs.ErrChainNotifierShuttingDown 416 } 417 418 reorg := &SpendEvent{ 419 Event: &SpendEvent_Reorg{Reorg: &Reorg{}}, 420 } 421 if err := spendStream.Send(reorg); err != nil { 422 return err 423 } 424 425 // The spending transaction of the requests has confirmed 426 // on-chain and is no longer under the risk of being reorged out 427 // of the chain, so we can safely exit. 428 case _, ok := <-spendEvent.Done: 429 if !ok { 430 return chainntnfs.ErrChainNotifierShuttingDown 431 } 432 433 return nil 434 435 // The response stream's context for whatever reason has been 436 // closed. If context is closed by an exceeded deadline we will 437 // return an error. 438 case <-spendStream.Context().Done(): 439 if errors.Is(spendStream.Context().Err(), context.Canceled) { 440 return nil 441 } 442 return spendStream.Context().Err() 443 444 // The server has been requested to shut down. 445 case <-s.quit: 446 return ErrChainNotifierServerShuttingDown 447 } 448 } 449 } 450 451 // RegisterBlockEpochNtfn is a synchronous response-streaming RPC that registers 452 // an intent for a client to be notified of blocks in the chain. The stream will 453 // return a hash and height tuple of a block for each new/stale block in the 454 // chain. It is the client's responsibility to determine whether the tuple 455 // returned is for a new or stale block in the chain. 456 // 457 // A client can also request a historical backlog of blocks from a particular 458 // point. This allows clients to be idempotent by ensuring that they do not 459 // missing processing a single block within the chain. 460 // 461 // NOTE: This is part of the chainrpc.ChainNotifierService interface. 462 func (s *Server) RegisterBlockEpochNtfn(in *BlockEpoch, 463 epochStream ChainNotifier_RegisterBlockEpochNtfnServer) error { 464 465 if !s.cfg.ChainNotifier.Started() { 466 return ErrChainNotifierServerNotActive 467 } 468 469 // We'll start by reconstructing the RPC request into what the 470 // underlying ChainNotifier expects. 471 var hash chainhash.Hash 472 copy(hash[:], in.Hash) 473 474 // If the request isn't for a zero hash and a zero height, then we 475 // should deliver a backlog of notifications from the given block 476 // (hash/height tuple) until tip, and continue delivering epochs for 477 // new blocks. 478 var blockEpoch *chainntnfs.BlockEpoch 479 if hash != chainntnfs.ZeroHash && in.Height != 0 { 480 blockEpoch = &chainntnfs.BlockEpoch{ 481 Hash: &hash, 482 Height: int32(in.Height), 483 } 484 } 485 486 epochEvent, err := s.cfg.ChainNotifier.RegisterBlockEpochNtfn(blockEpoch) 487 if err != nil { 488 return err 489 } 490 defer epochEvent.Cancel() 491 492 for { 493 select { 494 // A notification for a block has been received. This block can 495 // either be a new block or stale. 496 case blockEpoch, ok := <-epochEvent.Epochs: 497 if !ok { 498 return chainntnfs.ErrChainNotifierShuttingDown 499 } 500 501 epoch := &BlockEpoch{ 502 Hash: blockEpoch.Hash[:], 503 Height: uint32(blockEpoch.Height), 504 } 505 if err := epochStream.Send(epoch); err != nil { 506 return err 507 } 508 509 // The response stream's context for whatever reason has been 510 // closed. If context is closed by an exceeded deadline we will 511 // return an error. 512 case <-epochStream.Context().Done(): 513 if errors.Is(epochStream.Context().Err(), context.Canceled) { 514 return nil 515 } 516 return epochStream.Context().Err() 517 518 // The server has been requested to shut down. 519 case <-s.quit: 520 return ErrChainNotifierServerShuttingDown 521 } 522 } 523 }