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  }