diff --git a/lnd.go b/lnd.go index b0c741fb..d3edde16 100644 --- a/lnd.go +++ b/lnd.go @@ -54,31 +54,15 @@ import ( "github.com/lightningnetwork/lnd/watchtower/wtdb" ) -// WalletUnlockerAuthOptions returns a list of DialOptions that can be used to -// authenticate with the wallet unlocker service. -// -// NOTE: This should only be called after the WalletUnlocker listener has -// signaled it is ready. -func WalletUnlockerAuthOptions(cfg *Config) ([]grpc.DialOption, error) { - creds, err := credentials.NewClientTLSFromFile(cfg.TLSCertPath, "") - if err != nil { - return nil, fmt.Errorf("unable to read TLS cert: %v", err) - } - - // Create a dial options array with the TLS credentials. - opts := []grpc.DialOption{ - grpc.WithTransportCredentials(creds), - } - - return opts, nil -} - // AdminAuthOptions returns a list of DialOptions that can be used to // authenticate with the RPC server with admin capabilities. +// skipMacaroons=true should be set if we don't want to include macaroons with +// the auth options. This is needed for instance for the WalletUnlocker +// service, which must be usable also before macaroons are created. // // NOTE: This should only be called after the RPCListener has signaled it is // ready. -func AdminAuthOptions(cfg *Config) ([]grpc.DialOption, error) { +func AdminAuthOptions(cfg *Config, skipMacaroons bool) ([]grpc.DialOption, error) { creds, err := credentials.NewClientTLSFromFile(cfg.TLSCertPath, "") if err != nil { return nil, fmt.Errorf("unable to read TLS cert: %v", err) @@ -90,7 +74,7 @@ func AdminAuthOptions(cfg *Config) ([]grpc.DialOption, error) { } // Get the admin macaroon if macaroons are active. - if !cfg.NoMacaroons { + if !skipMacaroons && !cfg.NoMacaroons { // Load the adming macaroon file. macBytes, err := ioutil.ReadFile(cfg.AdminMacPath) if err != nil { @@ -169,10 +153,6 @@ type ListenerWithSignal struct { // ListenerCfg is a wrapper around custom listeners that can be passed to lnd // when calling its main method. type ListenerCfg struct { - // WalletUnlocker can be set to the listener to use for the wallet - // unlocker. If nil a regular network listener will be created. - WalletUnlocker *ListenerWithSignal - // RPCListener can be set to the listener to use for the RPC server. If // nil a regular network listener will be created. RPCListener *ListenerWithSignal diff --git a/lnrpc/Dockerfile b/lnrpc/Dockerfile index c871b9b0..abe38567 100644 --- a/lnrpc/Dockerfile +++ b/lnrpc/Dockerfile @@ -11,7 +11,7 @@ RUN apt-get update && apt-get install -y \ ARG PROTOC_GEN_VERSION ARG GRPC_GATEWAY_VERSION -ENV FALAFEL_VERSION="v0.7.1" +ENV FALAFEL_VERSION="v0.8.1" ENV GOCACHE=/tmp/build/.cache ENV GOMODCACHE=/tmp/build/.modcache diff --git a/mobile/bindings.go b/mobile/bindings.go index c157edcc..2f769a25 100644 --- a/mobile/bindings.go +++ b/mobile/bindings.go @@ -3,29 +3,34 @@ package lndmobile import ( + "errors" "fmt" "os" "strings" + "sync/atomic" flags "github.com/jessevdk/go-flags" "github.com/lightningnetwork/lnd" "github.com/lightningnetwork/lnd/signal" + "google.golang.org/grpc" ) +// lndStarted will be used atomically to ensure only a singel lnd instance is +// attempted to be started at once. +var lndStarted int32 + // Start starts lnd in a new goroutine. // // extraArgs can be used to pass command line arguments to lnd that will // override what is found in the config file. Example: // extraArgs = "--bitcoin.testnet --lnddir=\"/tmp/folder name/\" --profile=5050" // -// The unlockerReady callback is called when the WalletUnlocker service is -// ready, and rpcReady is called after the wallet has been unlocked and lnd is -// ready to accept RPC calls. +// The rpcReady is called lnd is ready to accept RPC calls. // // NOTE: On mobile platforms the '--lnddir` argument should be set to the // current app directory in order to ensure lnd has the permissions needed to // write to it. -func Start(extraArgs string, unlockerReady, rpcReady Callback) { +func Start(extraArgs string, rpcReady Callback) { // Split the argument string on "--" to get separated command line // arguments. var splitArgs []string @@ -62,29 +67,37 @@ func Start(extraArgs string, unlockerReady, rpcReady Callback) { return } - // Set up channels that will be notified when the RPC servers are ready - // to accept calls. + // Set a channel that will be notified when the RPC server is ready to + // accept calls. var ( - unlockerListening = make(chan struct{}) - rpcListening = make(chan struct{}) + rpcListening = make(chan struct{}) + quit = make(chan struct{}) ) - // We call the main method with the custom in-memory listeners called - // by the mobile APIs, such that the grpc server will use these. + // We call the main method with the custom in-memory listener called by + // the mobile APIs, such that the grpc server will use it. cfg := lnd.ListenerCfg{ - WalletUnlocker: &lnd.ListenerWithSignal{ - Listener: walletUnlockerLis, - Ready: unlockerListening, - }, RPCListener: &lnd.ListenerWithSignal{ Listener: lightningLis, Ready: rpcListening, }, } + // We only support a single lnd instance at a time (singleton) for now, + // so we make sure to return immediately if it has already been + // started. + if !atomic.CompareAndSwapInt32(&lndStarted, 0, 1) { + err := errors.New("lnd already started") + rpcReady.OnError(err) + return + } + // Call the "real" main in a nested manner so the defers will properly // be executed in the case of a graceful shutdown. go func() { + defer atomic.StoreInt32(&lndStarted, 0) + defer close(quit) + if err := lnd.Main( loadedConfig, cfg, shutdownInterceptor, ); err != nil { @@ -98,40 +111,37 @@ func Start(extraArgs string, unlockerReady, rpcReady Callback) { } }() - // Finally we start two go routines that will call the provided - // callbacks when the RPC servers are ready to accept calls. - go func() { - <-unlockerListening + // By default we'll apply the admin auth options, which will include + // macaroons. + setDefaultDialOption( + func() ([]grpc.DialOption, error) { + return lnd.AdminAuthOptions(loadedConfig, false) + }, + ) - // We must set the TLS certificates in order to properly - // authenticate with the wallet unlocker service. - auth, err := lnd.WalletUnlockerAuthOptions(loadedConfig) - if err != nil { - unlockerReady.OnError(err) + // For the WalletUnlocker and StateService, the macaroons might not be + // available yet when called, so we use a more restricted set of + // options that don't include them. + setWalletUnlockerDialOption( + func() ([]grpc.DialOption, error) { + return lnd.AdminAuthOptions(loadedConfig, true) + }, + ) + setStateDialOption( + func() ([]grpc.DialOption, error) { + return lnd.AdminAuthOptions(loadedConfig, true) + }, + ) + + // Finally we start a go routine that will call the provided callback + // when the RPC server is ready to accept calls. + go func() { + select { + case <-rpcListening: + case <-quit: return } - // Add the auth options to the listener's dial options. - addWalletUnlockerLisDialOption(auth...) - - unlockerReady.OnResponse([]byte{}) - }() - - go func() { - <-rpcListening - - // Now that the RPC server is ready, we can get the needed - // authentication options, and add them to the global dial - // options. - auth, err := lnd.AdminAuthOptions(loadedConfig) - if err != nil { - rpcReady.OnError(err) - return - } - - // Add the auth options to the listener's dial options. - addLightningLisDialOption(auth...) - rpcReady.OnResponse([]byte{}) }() } diff --git a/mobile/gen_bindings.sh b/mobile/gen_bindings.sh index 4b1a1637..7eaa1be7 100755 --- a/mobile/gen_bindings.sh +++ b/mobile/gen_bindings.sh @@ -16,7 +16,7 @@ then version="v$($falafel -v)" if [ $version != $falafelVersion ] then - echo "falafel version $falafelVersion required" + echo "falafel version $falafelVersion required, had $version" exit 1 fi echo "Using plugin $falafel $version" @@ -33,13 +33,13 @@ target_pkg="github.com/lightningnetwork/lnd/lnrpc" # A mapping from grpc service to name of the custom listeners. The grpc server # must be configured to listen on these. -listeners="lightning=lightningLis walletunlocker=walletUnlockerLis" +listeners="lightning=lightningLis walletunlocker=lightningLis state=lightningLis" # Set to 1 to create boiler plate grpc client code and listeners. If more than # one proto file is being parsed, it should only be done once. mem_rpc=1 -PROTOS="rpc.proto walletunlocker.proto" +PROTOS="rpc.proto walletunlocker.proto stateservice.proto" opts="package_name=$pkg,target_package=$target_pkg,listeners=$listeners,mem_rpc=$mem_rpc"