github.com/decred/dcrlnd@v0.7.6/mobile/bindings.go (about)

     1  //go:build mobile
     2  // +build mobile
     3  
     4  package dcrlndmobile
     5  
     6  import (
     7  	"errors"
     8  	"fmt"
     9  	"os"
    10  	"strings"
    11  	"sync/atomic"
    12  
    13  	lnd "github.com/decred/dcrlnd"
    14  	"github.com/decred/dcrlnd/signal"
    15  	flags "github.com/jessevdk/go-flags"
    16  	"google.golang.org/grpc"
    17  )
    18  
    19  // lndStarted will be used atomically to ensure only a singel lnd instance is
    20  // attempted to be started at once.
    21  var lndStarted int32
    22  
    23  // Start starts lnd in a new goroutine.
    24  //
    25  // extraArgs can be used to pass command line arguments to lnd that will
    26  // override what is found in the config file. Example:
    27  //
    28  //	extraArgs = "--bitcoin.testnet --lnddir=\"/tmp/folder name/\" --profile=5050"
    29  //
    30  // The rpcReady is called lnd is ready to accept RPC calls.
    31  //
    32  // NOTE: On mobile platforms the '--lnddir` argument should be set to the
    33  // current app directory in order to ensure lnd has the permissions needed to
    34  // write to it.
    35  func Start(extraArgs string, rpcReady Callback) {
    36  	// We only support a single lnd instance at a time (singleton) for now,
    37  	// so we make sure to return immediately if it has already been
    38  	// started.
    39  	if !atomic.CompareAndSwapInt32(&lndStarted, 0, 1) {
    40  		err := errors.New("lnd already started")
    41  		rpcReady.OnError(err)
    42  		return
    43  	}
    44  
    45  	// (Re-)initialize the in-mem gRPC listeners we're going to give to lnd.
    46  	// This is required each time lnd is started, because when lnd shuts
    47  	// down, the in-mem listeners are closed.
    48  	RecreateListeners()
    49  
    50  	// Split the argument string on "--" to get separated command line
    51  	// arguments.
    52  	var splitArgs []string
    53  	for _, a := range strings.Split(extraArgs, "--") {
    54  		// Trim any whitespace space, and ignore empty params.
    55  		a := strings.TrimSpace(a)
    56  		if a == "" {
    57  			continue
    58  		}
    59  
    60  		// Finally we prefix any non-empty string with -- to mimic the
    61  		// regular command line arguments.
    62  		splitArgs = append(splitArgs, "--"+a)
    63  	}
    64  
    65  	// Add the extra arguments to os.Args, as that will be parsed in
    66  	// LoadConfig below.
    67  	os.Args = append(os.Args, splitArgs...)
    68  
    69  	// Hook interceptor for os signals.
    70  	shutdownInterceptor, err := signal.Intercept()
    71  	if err != nil {
    72  		atomic.StoreInt32(&lndStarted, 0)
    73  		_, _ = fmt.Fprintln(os.Stderr, err)
    74  		rpcReady.OnError(err)
    75  		return
    76  	}
    77  
    78  	// Load the configuration, and parse the extra arguments as command
    79  	// line options. This function will also set up logging properly.
    80  	loadedConfig, err := lnd.LoadConfig(shutdownInterceptor)
    81  	if err != nil {
    82  		atomic.StoreInt32(&lndStarted, 0)
    83  		_, _ = fmt.Fprintln(os.Stderr, err)
    84  		rpcReady.OnError(err)
    85  		return
    86  	}
    87  
    88  	// Set a channel that will be notified when the RPC server is ready to
    89  	// accept calls.
    90  	var (
    91  		rpcListening = make(chan struct{})
    92  		quit         = make(chan struct{})
    93  	)
    94  
    95  	// We call the main method with the custom in-memory listener called by
    96  	// the mobile APIs, such that the grpc server will use it.
    97  	cfg := lnd.ListenerCfg{
    98  		RPCListeners: []*lnd.ListenerWithSignal{{
    99  			Listener: lightningLis,
   100  			Ready:    rpcListening,
   101  		}},
   102  	}
   103  implCfg:
   104  	loadedConfig.ImplementationConfig(shutdownInterceptor)
   105  
   106  	// Call the "real" main in a nested manner so the defers will properly
   107  	// be executed in the case of a graceful shutdown.
   108  	go func() {
   109  		defer atomic.StoreInt32(&lndStarted, 0)
   110  		defer close(quit)
   111  
   112  		if err := lnd.Main(
   113  			loadedConfig, cfg, implCfg, shutdownInterceptor,
   114  		); err != nil {
   115  			if e, ok := err.(*flags.Error); ok &&
   116  				e.Type == flags.ErrHelp {
   117  			} else {
   118  				fmt.Fprintln(os.Stderr, err)
   119  			}
   120  			rpcReady.OnError(err)
   121  			return
   122  		}
   123  	}()
   124  
   125  	// By default we'll apply the admin auth options, which will include
   126  	// macaroons.
   127  	setDefaultDialOption(
   128  		func() ([]grpc.DialOption, error) {
   129  			return lnd.AdminAuthOptions(loadedConfig, false)
   130  		},
   131  	)
   132  
   133  	// For the WalletUnlocker and StateService, the macaroons might not be
   134  	// available yet when called, so we use a more restricted set of
   135  	// options that don't include them.
   136  	setWalletUnlockerDialOption(
   137  		func() ([]grpc.DialOption, error) {
   138  			return lnd.AdminAuthOptions(loadedConfig, true)
   139  		},
   140  	)
   141  	setStateDialOption(
   142  		func() ([]grpc.DialOption, error) {
   143  			return lnd.AdminAuthOptions(loadedConfig, true)
   144  		},
   145  	)
   146  
   147  	// Finally we start a go routine that will call the provided callback
   148  	// when the RPC server is ready to accept calls.
   149  	go func() {
   150  		select {
   151  		case <-rpcListening:
   152  		case <-quit:
   153  			return
   154  		}
   155  
   156  		rpcReady.OnResponse([]byte{})
   157  	}()
   158  }