/ mobile / bindings.go
bindings.go
  1  //go:build mobile
  2  // +build mobile
  3  
  4  package lndmobile
  5  
  6  import (
  7  	"errors"
  8  	"fmt"
  9  	"os"
 10  	"strings"
 11  	"sync/atomic"
 12  
 13  	flags "github.com/jessevdk/go-flags"
 14  	"github.com/lightningnetwork/lnd"
 15  	"github.com/lightningnetwork/lnd/signal"
 16  	_ "golang.org/x/mobile/bind"
 17  	"google.golang.org/grpc"
 18  )
 19  
 20  // lndStarted will be used atomically to ensure only a single lnd instance is
 21  // attempted to be started at once.
 22  var lndStarted int32
 23  
 24  // Start starts lnd in a new goroutine.
 25  //
 26  // extraArgs can be used to pass command line arguments to lnd that will
 27  // override what is found in the config file. Example:
 28  //
 29  //	extraArgs = "--bitcoin.testnet --lnddir=\"/tmp/folder name/\" --profile=5050"
 30  //
 31  // The rpcReady is called lnd is ready to accept RPC calls.
 32  //
 33  // NOTE: On mobile platforms the '--lnddir` argument should be set to the
 34  // current app directory in order to ensure lnd has the permissions needed to
 35  // write to it.
 36  func Start(extraArgs string, rpcReady Callback) {
 37  	// We only support a single lnd instance at a time (singleton) for now,
 38  	// so we make sure to return immediately if it has already been
 39  	// started.
 40  	if !atomic.CompareAndSwapInt32(&lndStarted, 0, 1) {
 41  		err := errors.New("lnd already started")
 42  		rpcReady.OnError(err)
 43  		return
 44  	}
 45  
 46  	// (Re-)initialize the in-mem gRPC listeners we're going to give to lnd.
 47  	// This is required each time lnd is started, because when lnd shuts
 48  	// down, the in-mem listeners are closed.
 49  	RecreateListeners()
 50  
 51  	// Split the argument string on "--" to get separated command line
 52  	// arguments.
 53  	var splitArgs []string
 54  	for _, a := range strings.Split(extraArgs, "--") {
 55  		// Trim any whitespace space, and ignore empty params.
 56  		a := strings.TrimSpace(a)
 57  		if a == "" {
 58  			continue
 59  		}
 60  
 61  		// Finally we prefix any non-empty string with -- to mimic the
 62  		// regular command line arguments.
 63  		splitArgs = append(splitArgs, "--"+a)
 64  	}
 65  
 66  	// Add the extra arguments to os.Args, as that will be parsed in
 67  	// LoadConfig below.
 68  	os.Args = append(os.Args, splitArgs...)
 69  
 70  	// Hook interceptor for os signals.
 71  	shutdownInterceptor, err := signal.Intercept()
 72  	if err != nil {
 73  		atomic.StoreInt32(&lndStarted, 0)
 74  		_, _ = fmt.Fprintln(os.Stderr, err)
 75  		rpcReady.OnError(err)
 76  		return
 77  	}
 78  
 79  	// Load the configuration, and parse the extra arguments as command
 80  	// line options. This function will also set up logging properly.
 81  	loadedConfig, err := lnd.LoadConfig(shutdownInterceptor)
 82  	if err != nil {
 83  		atomic.StoreInt32(&lndStarted, 0)
 84  		_, _ = fmt.Fprintln(os.Stderr, err)
 85  		rpcReady.OnError(err)
 86  		return
 87  	}
 88  
 89  	// Set a channel that will be notified when the RPC server is ready to
 90  	// accept calls.
 91  	var (
 92  		rpcListening = make(chan struct{})
 93  		quit         = make(chan struct{})
 94  	)
 95  
 96  	// We call the main method with the custom in-memory listener called by
 97  	// the mobile APIs, such that the grpc server will use it.
 98  	cfg := lnd.ListenerCfg{
 99  		RPCListeners: []*lnd.ListenerWithSignal{{
100  			Listener: lightningLis,
101  			Ready:    rpcListening,
102  		}},
103  	}
104  	implCfg := 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  }