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 }