// +build walletrpc package walletrpc import ( "bytes" fmt "fmt" "io/ioutil" "os" "path/filepath" "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/lnrpc" signrpc "github.com/lightningnetwork/lnd/lnrpc/signrpc" "github.com/lightningnetwork/lnd/lnwallet" context "golang.org/x/net/context" grpc "google.golang.org/grpc" "gopkg.in/macaroon-bakery.v2/bakery" ) const ( // subServerName is the name of the sub rpc server. We'll use this name // to register ourselves, and we also require that the main // SubServerConfigDispatcher instance recognize as the name of our subServerName = "WalletKitRPC" ) var ( // macaroonOps are the set of capabilities that our minted macaroon (if // it doesn't already exist) will have. macaroonOps = []bakery.Op{ { Entity: "address", Action: "write", }, { Entity: "address", Action: "read", }, { Entity: "onchain", Action: "write", }, { Entity: "onchain", Action: "read", }, } // macPermissions maps RPC calls to the permissions they require. macPermissions = map[string][]bakery.Op{ "/walletrpc.WalletKit/DeriveNextKey": {{ Entity: "address", Action: "read", }}, "/walletrpc.WalletKit/DeriveKey": {{ Entity: "address", Action: "read", }}, "/walletrpc.WalletKit/NextAddr": {{ Entity: "address", Action: "read", }}, "/walletrpc.WalletKit/PublishTransaction": {{ Entity: "onchain", Action: "write", }}, "/walletrpc.WalletKit/SendOutputs": {{ Entity: "onchain", Action: "write", }}, "/walletrpc.WalletKit/EstimateFee": {{ Entity: "onchain", Action: "read", }}, } // DefaultWalletKitMacFilename is the default name of the wallet kit // macaroon that we expect to find via a file handle within the main // configuration file in this package. DefaultWalletKitMacFilename = "walletkit.macaroon" ) // WalletKit is a sub-RPC server that exposes a tool kit which allows clients // to execute common wallet operations. This includes requesting new addresses, // keys (for contracts!), and publishing transactions. type WalletKit struct { cfg *Config } // A compile time check to ensure that WalletKit fully implements the // WalletKitServer gRPC service. var _ WalletKitServer = (*WalletKit)(nil) // fileExists reports whether the named file or directory exists. func fileExists(name string) bool { if _, err := os.Stat(name); err != nil { if os.IsNotExist(err) { return false } } return true } // New creates a new instance of the WalletKit sub-RPC server. func New(cfg *Config) (*WalletKit, lnrpc.MacaroonPerms, error) { // If the path of the wallet kit macaroon wasn't specified, then we'll // assume that it's found at the default network directory. if cfg.WalletKitMacPath == "" { cfg.WalletKitMacPath = filepath.Join( cfg.NetworkDir, DefaultWalletKitMacFilename, ) } // Now that we know the full path of the wallet kit macaroon, we can // check to see if we need to create it or not. macFilePath := cfg.WalletKitMacPath if !fileExists(macFilePath) && cfg.MacService != nil { log.Infof("Baking macaroons for WaleltKit RPC Server at: %v", macFilePath) // At this point, we know that the wallet kit macaroon doesn't // yet, exist, so we need to create it with the help of the // main macaroon service. walletKitMac, err := cfg.MacService.Oven.NewMacaroon( context.Background(), bakery.LatestVersion, nil, macaroonOps..., ) if err != nil { return nil, nil, err } walletKitMacBytes, err := walletKitMac.M().MarshalBinary() if err != nil { return nil, nil, err } err = ioutil.WriteFile(macFilePath, walletKitMacBytes, 0644) if err != nil { os.Remove(macFilePath) return nil, nil, err } } walletKit := &WalletKit{ cfg: cfg, } return walletKit, macPermissions, nil } // Start launches any helper goroutines required for the sub-server to function. // // NOTE: This is part of the lnrpc.SubServer interface. func (w *WalletKit) Start() error { return nil } // Stop signals any active goroutines for a graceful closure. // // NOTE: This is part of the lnrpc.SubServer interface. func (w *WalletKit) Stop() error { return nil } // Name returns a unique string representation of the sub-server. This can be // used to identify the sub-server and also de-duplicate them. // // NOTE: This is part of the lnrpc.SubServer interface. func (w *WalletKit) Name() string { return subServerName } // RegisterWithRootServer will be called by the root gRPC server to direct a // sub RPC server to register itself with the main gRPC root server. Until this // is called, each sub-server won't be able to have requests routed towards it. // // NOTE: This is part of the lnrpc.SubServer interface. func (w *WalletKit) RegisterWithRootServer(grpcServer *grpc.Server) error { // We make sure that we register it with the main gRPC server to ensure // all our methods are routed properly. RegisterWalletKitServer(grpcServer, w) log.Debugf("WalletKit RPC server successfully registered with " + "root gRPC server") return nil } // DeriveNextKey attempts to derive the *next* key within the key family // (account in BIP43) specified. This method should return the next external // child within this branch. func (w *WalletKit) DeriveNextKey(ctx context.Context, req *KeyReq) (*signrpc.KeyDescriptor, error) { nextKeyDesc, err := w.cfg.KeyRing.DeriveNextKey( keychain.KeyFamily(req.KeyFamily), ) if err != nil { return nil, err } return &signrpc.KeyDescriptor{ KeyLoc: &signrpc.KeyLocator{ KeyFamily: int32(nextKeyDesc.Family), KeyIndex: int32(nextKeyDesc.Index), }, RawKeyBytes: nextKeyDesc.PubKey.SerializeCompressed(), }, nil } // DeriveKey attempts to derive an arbitrary key specified by the passed // KeyLocator. func (w *WalletKit) DeriveKey(ctx context.Context, req *signrpc.KeyLocator) (*signrpc.KeyDescriptor, error) { keyDesc, err := w.cfg.KeyRing.DeriveKey(keychain.KeyLocator{ Family: keychain.KeyFamily(req.KeyFamily), Index: uint32(req.KeyIndex), }) if err != nil { return nil, err } return &signrpc.KeyDescriptor{ KeyLoc: &signrpc.KeyLocator{ KeyFamily: int32(keyDesc.Family), KeyIndex: int32(keyDesc.Index), }, RawKeyBytes: keyDesc.PubKey.SerializeCompressed(), }, nil } // NextAddr returns the next unused address within the wallet. func (w *WalletKit) NextAddr(ctx context.Context, req *AddrRequest) (*AddrResponse, error) { addr, err := w.cfg.Wallet.NewAddress(lnwallet.WitnessPubKey, false) if err != nil { return nil, err } return &AddrResponse{ Addr: addr.String(), }, nil } // Attempts to publish the passed transaction to the network. Once this returns // without an error, the wallet will continually attempt to re-broadcast the // transaction on start up, until it enters the chain. func (w *WalletKit) PublishTransaction(ctx context.Context, req *Transaction) (*PublishResponse, error) { switch { // If the client doesn't specify a transaction, then there's nothing to // publish. case len(req.TxHex) == 0: return nil, fmt.Errorf("must provide a transaction to " + "publish") } tx := &wire.MsgTx{} txReader := bytes.NewReader(req.TxHex) if err := tx.Deserialize(txReader); err != nil { return nil, err } err := w.cfg.Wallet.PublishTransaction(tx) if err != nil { return nil, err } return &PublishResponse{}, nil } // SendOutputs is similar to the existing sendmany call in Bitcoind, and allows // the caller to create a transaction that sends to several outputs at once. // This is ideal when wanting to batch create a set of transactions. func (w *WalletKit) SendOutputs(ctx context.Context, req *SendOutputsRequest) (*SendOutputsResponse, error) { switch { // If the client didn't specify any outputs to create, then we can't // proceed . case len(req.Outputs) == 0: return nil, fmt.Errorf("must specify at least one output " + "to create") } // Before we can request this transaction to be created, we'll need to // amp the protos back into the format that the internal wallet will // recognize. outputsToCreate := make([]*wire.TxOut, 0, len(req.Outputs)) for _, output := range req.Outputs { outputsToCreate = append(outputsToCreate, &wire.TxOut{ Value: output.Value, PkScript: output.PkScript, }) } // Now that we have the outputs mapped, we can request that the wallet // attempt to create this transaction. tx, err := w.cfg.Wallet.SendOutputs( outputsToCreate, lnwallet.SatPerKWeight(req.SatPerKw), ) if err != nil { return nil, err } var b bytes.Buffer if err := tx.Serialize(&b); err != nil { return nil, err } return &SendOutputsResponse{ RawTx: b.Bytes(), }, nil } // EstimateFee attempts to query the internal fee estimator of the wallet to // determine the fee (in sat/kw) to attach to a transaction in order to achieve // the confirmation target. func (w *WalletKit) EstimateFee(ctx context.Context, req *EstimateFeeRequest) (*EstimateFeeResponse, error) { switch { // A confirmation target of zero doesn't make any sense. Similarly, we // reject confirmation targets of 1 as they're unreasonable. case req.ConfTarget == 0 || req.ConfTarget == 1: return nil, fmt.Errorf("confirmation target must be greater " + "than 1") } satPerKw, err := w.cfg.FeeEstimator.EstimateFeePerKW( uint32(req.ConfTarget), ) if err != nil { return nil, err } return &EstimateFeeResponse{ SatPerKw: int64(satPerKw), }, nil }