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 }