566cd86a1d
This commit adds a REST interface to the existing gRPC server by employing a simple http proxy auto-generated from the existing protobuf files. Currently full-support for any streaming RPC’s are currently untested. In addition to auto-generating a REST proxy server, a swagger.json is also generated which allows for gRPC-like native objects with higher-level clients, and also for auto-generated documentation. Due to limitations with accepting raw byte strings as parameters, some RPC’s have been modified to take both raw-bytes and string arguments. Additionally a new RPC has been added ‘NewWitnessAddress’ since the proxy doesn’t currently support enum-based arguments. Currently the proxy server is embedded within the daemon as an active HTTP server, however we may want to package the proxy server as a separate binary in the future. Similarly, we may want to add additional configuration information which controls the optional inclusion of the REST proxy. Atm, just like the current gRPC interface, the REST API is fully unauthenticated. Before moving to an initial alpha release after making the necessary changes to meet the spec drafted in Milan, authentication of the RPC interfaces will be addressed.
232 lines
6.5 KiB
Go
232 lines
6.5 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"net"
|
|
"net/http"
|
|
_ "net/http/pprof"
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strconv"
|
|
|
|
"google.golang.org/grpc"
|
|
|
|
proxy "github.com/grpc-ecosystem/grpc-gateway/runtime"
|
|
"github.com/lightningnetwork/lnd/chainntnfs/btcdnotify"
|
|
"github.com/lightningnetwork/lnd/channeldb"
|
|
"github.com/lightningnetwork/lnd/lnrpc"
|
|
"github.com/lightningnetwork/lnd/lnwallet"
|
|
"github.com/lightningnetwork/lnd/lnwallet/btcwallet"
|
|
|
|
"github.com/roasbeef/btcrpcclient"
|
|
)
|
|
|
|
var (
|
|
cfg *config
|
|
shutdownChannel = make(chan struct{})
|
|
)
|
|
|
|
// lndMain is the true entry point for lnd. This function is required since
|
|
// defers created in the top-level scope of a main method aren't executed if
|
|
// os.Exit() is called.
|
|
func lndMain() error {
|
|
// Load the configuration, and parse any command line options. This
|
|
// function will also set up logging properly.
|
|
loadedConfig, err := loadConfig()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
cfg = loadedConfig
|
|
defer backendLog.Flush()
|
|
|
|
// Show version at startup.
|
|
ltndLog.Infof("Version %s", version())
|
|
|
|
if loadedConfig.SPVMode == true {
|
|
shell(loadedConfig.SPVHostAdr, activeNetParams.Params)
|
|
return err
|
|
}
|
|
|
|
// Enable http profiling server if requested.
|
|
if cfg.Profile != "" {
|
|
go func() {
|
|
listenAddr := net.JoinHostPort("", cfg.Profile)
|
|
profileRedirect := http.RedirectHandler("/debug/pprof",
|
|
http.StatusSeeOther)
|
|
http.Handle("/", profileRedirect)
|
|
fmt.Println(http.ListenAndServe(listenAddr, nil))
|
|
}()
|
|
}
|
|
|
|
// Open the channeldb, which is dedicated to storing channel, and
|
|
// network related meta-data.
|
|
chanDB, err := channeldb.Open(loadedConfig.DataDir, activeNetParams.Params)
|
|
if err != nil {
|
|
fmt.Println("unable to open channeldb: ", err)
|
|
return err
|
|
}
|
|
defer chanDB.Close()
|
|
|
|
// Next load btcd's TLS cert for the RPC connection. If a raw cert was
|
|
// specified in the config, then we'll se that directly. Otherwise, we
|
|
// attempt to read the cert from the path specified in the config.
|
|
var rpcCert []byte
|
|
if loadedConfig.RawRPCCert != "" {
|
|
rpcCert, err = hex.DecodeString(loadedConfig.RawRPCCert)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
certFile, err := os.Open(loadedConfig.RPCCert)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
rpcCert, err = ioutil.ReadAll(certFile)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := certFile.Close(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
rpcIP, err := net.LookupHost(loadedConfig.RPCHost)
|
|
if err != nil {
|
|
fmt.Printf("unable to resolve rpc host: %v", err)
|
|
return err
|
|
}
|
|
|
|
btcdHost := fmt.Sprintf("%v:%v", loadedConfig.RPCHost, activeNetParams.rpcPort)
|
|
btcdUser := loadedConfig.RPCUser
|
|
btcdPass := loadedConfig.RPCPass
|
|
|
|
// TODO(roasbeef): parse config here and select chosen notifier instead
|
|
rpcConfig := &btcrpcclient.ConnConfig{
|
|
Host: btcdHost,
|
|
Endpoint: "ws",
|
|
User: btcdUser,
|
|
Pass: btcdPass,
|
|
Certificates: rpcCert,
|
|
DisableTLS: false,
|
|
DisableConnectOnNew: true,
|
|
DisableAutoReconnect: false,
|
|
}
|
|
notifier, err := btcdnotify.New(rpcConfig)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// TODO(roasbeef): paarse config here select chosen WalletController
|
|
walletConfig := &btcwallet.Config{
|
|
PrivatePass: []byte("hello"),
|
|
DataDir: filepath.Join(loadedConfig.DataDir, "lnwallet"),
|
|
RpcHost: fmt.Sprintf("%v:%v", rpcIP[0], activeNetParams.rpcPort),
|
|
RpcUser: loadedConfig.RPCUser,
|
|
RpcPass: loadedConfig.RPCPass,
|
|
CACert: rpcCert,
|
|
NetParams: activeNetParams.Params,
|
|
}
|
|
wc, err := btcwallet.New(walletConfig)
|
|
if err != nil {
|
|
fmt.Printf("unable to create wallet controller: %v\n", err)
|
|
return err
|
|
}
|
|
signer := wc
|
|
bio := wc
|
|
|
|
// Create, and start the lnwallet, which handles the core payment
|
|
// channel logic, and exposes control via proxy state machines.
|
|
wallet, err := lnwallet.NewLightningWallet(chanDB, notifier,
|
|
wc, signer, bio, activeNetParams.Params)
|
|
if err != nil {
|
|
fmt.Printf("unable to create wallet: %v\n", err)
|
|
return err
|
|
}
|
|
if err := wallet.Startup(); err != nil {
|
|
fmt.Printf("unable to start wallet: %v\n", err)
|
|
return err
|
|
}
|
|
ltndLog.Info("LightningWallet opened")
|
|
|
|
// Set up the core server which will listen for incoming peer
|
|
// connections.
|
|
defaultListenAddrs := []string{
|
|
net.JoinHostPort("", strconv.Itoa(loadedConfig.PeerPort)),
|
|
}
|
|
server, err := newServer(defaultListenAddrs, notifier, bio, wallet, chanDB)
|
|
if err != nil {
|
|
srvrLog.Errorf("unable to create server: %v\n", err)
|
|
return err
|
|
}
|
|
if err := server.Start(); err != nil {
|
|
srvrLog.Errorf("unable to create to start: %v\n", err)
|
|
return err
|
|
}
|
|
|
|
addInterruptHandler(func() {
|
|
ltndLog.Infof("Gracefully shutting down the server...")
|
|
server.Stop()
|
|
server.WaitForShutdown()
|
|
})
|
|
|
|
// Initialize, and register our implementation of the gRPC server.
|
|
var opts []grpc.ServerOption
|
|
grpcServer := grpc.NewServer(opts...)
|
|
lnrpc.RegisterLightningServer(grpcServer, server.rpcServer)
|
|
|
|
// Next, Start the grpc server listening for HTTP/2 connections.
|
|
grpcEndpoint := fmt.Sprintf("localhost:%d", loadedConfig.RPCPort)
|
|
lis, err := net.Listen("tcp", grpcEndpoint)
|
|
if err != nil {
|
|
fmt.Printf("failed to listen: %v", err)
|
|
return err
|
|
}
|
|
go func() {
|
|
rpcsLog.Infof("RPC server listening on %s", lis.Addr())
|
|
grpcServer.Serve(lis)
|
|
}()
|
|
|
|
// Finally, start the REST proxy for our gRPC server above.
|
|
ctx := context.Background()
|
|
ctx, cancel := context.WithCancel(ctx)
|
|
defer cancel()
|
|
mux := proxy.NewServeMux()
|
|
swaggerPattern := proxy.MustPattern(proxy.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "swagger"}, ""))
|
|
// TODO(roasbeef): accept path to swagger file as command-line option
|
|
mux.Handle("GET", swaggerPattern, func(w http.ResponseWriter, r *http.Request, p map[string]string) {
|
|
http.ServeFile(w, r, "lnrpc/rpc.swagger.json")
|
|
})
|
|
proxyOpts := []grpc.DialOption{grpc.WithInsecure()}
|
|
err = lnrpc.RegisterLightningHandlerFromEndpoint(ctx, mux, grpcEndpoint, proxyOpts)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
go func() {
|
|
rpcsLog.Infof("gRPC proxy started")
|
|
http.ListenAndServe(":8080", mux)
|
|
}()
|
|
|
|
// Wait for shutdown signal from either a graceful server stop or from
|
|
// the interrupt handler.
|
|
<-shutdownChannel
|
|
ltndLog.Info("Shutdown complete")
|
|
return nil
|
|
}
|
|
|
|
func main() {
|
|
// Use all processor cores.
|
|
// TODO(roasbeef): remove this if required version # is > 1.6?
|
|
runtime.GOMAXPROCS(runtime.NumCPU())
|
|
|
|
// Call the "real" main in a nested manner so the defers will properly
|
|
// be executed in the case of a graceful shutdown.
|
|
if err := lndMain(); err != nil {
|
|
fmt.Fprintln(os.Stderr, err)
|
|
os.Exit(1)
|
|
}
|
|
}
|