lndclient: move to github.com/lightninglabs/lndclient

pull/237/head
Joost Jager 4 years ago
parent d4046a6053
commit 0c9fcd790e
No known key found for this signature in database
GPG Key ID: A61B9D4C393C59C7

@ -9,7 +9,7 @@ import (
"time"
"github.com/btcsuite/btcutil"
"github.com/lightninglabs/loop/lndclient"
"github.com/lightninglabs/lndclient"
"github.com/lightninglabs/loop/loopdb"
"github.com/lightninglabs/loop/lsat"
"github.com/lightninglabs/loop/swap"

@ -9,7 +9,7 @@ import (
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcutil"
"github.com/lightninglabs/loop/lndclient"
"github.com/lightninglabs/lndclient"
"github.com/lightninglabs/loop/loopdb"
"github.com/lightninglabs/loop/test"
"github.com/lightningnetwork/lnd/lnrpc"

@ -3,7 +3,7 @@ package loop
import (
"time"
"github.com/lightninglabs/loop/lndclient"
"github.com/lightninglabs/lndclient"
"github.com/lightninglabs/loop/loopdb"
"github.com/lightninglabs/loop/lsat"
)

@ -7,7 +7,7 @@ import (
"sync/atomic"
"time"
"github.com/lightninglabs/loop/lndclient"
"github.com/lightninglabs/lndclient"
"github.com/lightninglabs/loop/loopdb"
"github.com/lightninglabs/loop/sweep"
"github.com/lightningnetwork/lnd/queue"

@ -8,16 +8,15 @@ require (
github.com/coreos/bbolt v1.3.3
github.com/fortytw2/leaktest v1.3.0
github.com/golang/protobuf v1.3.2
github.com/google/go-cmp v0.3.1 // indirect
github.com/grpc-ecosystem/grpc-gateway v1.14.3
github.com/jessevdk/go-flags v1.4.0
github.com/lightninglabs/lndclient v0.0.0-20200618122423-5d815058a719
github.com/lightninglabs/protobuf-hex-display v1.3.3-0.20191212020323-b444784ce75d
github.com/lightningnetwork/lnd v0.10.0-beta.rc6.0.20200615174244-103c59a4889f
github.com/lightningnetwork/lnd/queue v1.0.4
github.com/stretchr/testify v1.5.1
github.com/urfave/cli v1.20.0
golang.org/x/net v0.0.0-20191002035440-2ec189313ef0
golang.org/x/text v0.3.2 // indirect
google.golang.org/genproto v0.0.0-20190927181202-20e1ac93f88c
google.golang.org/grpc v1.24.0
gopkg.in/macaroon.v2 v2.1.0

@ -110,9 +110,8 @@ github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.2.1-0.20190312032427-6f77996f0c42/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
@ -168,6 +167,8 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf h1:HZKvJUHlcXI/f/O0Avg7t8sqkPo78HFzjmeYFl6DPnc=
github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf/go.mod h1:vxmQPeIQxPf6Jf9rM8R+B4rKBqLA2AjttNxkFBL2Plk=
github.com/lightninglabs/lndclient v0.0.0-20200618122423-5d815058a719 h1:RzyO65pK78xxSaekERq2V3g32GMBPc0MfxmM8MDMVmQ=
github.com/lightninglabs/lndclient v0.0.0-20200618122423-5d815058a719/go.mod h1:WQU2oE0eJIp7jtBOUnL7CO5/YoWrAqyH23A6M1r31i8=
github.com/lightninglabs/neutrino v0.11.0/go.mod h1:CuhF0iuzg9Sp2HO6ZgXgayviFTn1QHdSTJlMncK80wg=
github.com/lightninglabs/neutrino v0.11.1-0.20200316235139-bffc52e8f200 h1:j4iZ1XlUAPQmW6oSzMcJGILYsRHNs+4O3Gk+2Ms5Dww=
github.com/lightninglabs/neutrino v0.11.1-0.20200316235139-bffc52e8f200/go.mod h1:MlZmoKa7CJP3eR1s5yB7Rm5aSyadpKkxqAwLQmog7N0=
@ -311,13 +312,11 @@ golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2 h1:z99zHgr7hKfrUcX/KsoJk5FJfjTceCKIp96+biqP4To=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2 h1:+DCIGbF/swA92ohVg0//6X2IVY3KZs6p9mix0ziNYJM=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=

@ -1,136 +0,0 @@
package lndclient
import (
"fmt"
"io/ioutil"
"path/filepath"
"github.com/lightningnetwork/lnd/lncfg"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/macaroons"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
macaroon "gopkg.in/macaroon.v2"
)
// BasicClientOption is a functional option argument that allows adding arbitrary
// lnd basic client configuration overrides, without forcing existing users of
// NewBasicClient to update their invocation. These are always processed in
// order, with later options overriding earlier ones.
type BasicClientOption func(*basicClientOptions)
// basicClientOptions is a set of options that can configure the lnd client
// returned by NewBasicClient.
type basicClientOptions struct {
macFilename string
}
// defaultBasicClientOptions returns a basicClientOptions set to lnd basic client
// defaults.
func defaultBasicClientOptions() *basicClientOptions {
return &basicClientOptions{
macFilename: defaultAdminMacaroonFilename,
}
}
// MacFilename is a basic client option that sets the name of the macaroon file
// to use.
func MacFilename(macFilename string) BasicClientOption {
return func(bc *basicClientOptions) {
bc.macFilename = macFilename
}
}
// applyBasicClientOptions updates a basicClientOptions set with functional
// options.
func (bc *basicClientOptions) applyBasicClientOptions(options ...BasicClientOption) {
for _, option := range options {
option(bc)
}
}
// NewBasicClient creates a new basic gRPC client to lnd. We call this client
// "basic" as it falls back to expected defaults if the arguments aren't
// provided.
func NewBasicClient(lndHost, tlsPath, macDir, network string,
basicOptions ...BasicClientOption) (
lnrpc.LightningClient, error) {
conn, err := NewBasicConn(
lndHost, tlsPath, macDir, network, basicOptions...,
)
if err != nil {
return nil, err
}
return lnrpc.NewLightningClient(conn), nil
}
// NewBasicConn creates a new basic gRPC connection to lnd. We call this
// connection "basic" as it falls back to expected defaults if the arguments
// aren't provided.
func NewBasicConn(lndHost, tlsPath, macDir, network string,
basicOptions ...BasicClientOption) (
*grpc.ClientConn, error) {
if tlsPath == "" {
tlsPath = defaultTLSCertPath
}
// Load the specified TLS certificate and build transport credentials
creds, err := credentials.NewClientTLSFromFile(tlsPath, "")
if err != nil {
return nil, err
}
// Create a dial options array.
opts := []grpc.DialOption{
grpc.WithTransportCredentials(creds),
}
if macDir == "" {
macDir = filepath.Join(
defaultLndDir, defaultDataDir, defaultChainSubDir,
"bitcoin", network,
)
}
// Starting with the set of default options, we'll apply any specified
// functional options to the basic client.
bco := defaultBasicClientOptions()
bco.applyBasicClientOptions(basicOptions...)
macPath := filepath.Join(macDir, bco.macFilename)
// Load the specified macaroon file.
macBytes, err := ioutil.ReadFile(macPath)
if err == nil {
// Only if file is found
mac := &macaroon.Macaroon{}
if err = mac.UnmarshalBinary(macBytes); err != nil {
return nil, fmt.Errorf("unable to decode macaroon: %v",
err)
}
// Now we append the macaroon credentials to the dial options.
cred := macaroons.NewMacaroonCredential(mac)
opts = append(opts, grpc.WithPerRPCCredentials(cred))
opts = append(opts, grpc.WithDefaultCallOptions(maxMsgRecvSize))
}
// We need to use a custom dialer so we can also connect to unix sockets
// and not just TCP addresses.
opts = append(
opts, grpc.WithContextDialer(
lncfg.ClientAddressDialer(defaultRPCPort),
),
)
conn, err := grpc.Dial(lndHost, opts...)
if err != nil {
return nil, fmt.Errorf("unable to connect to RPC server: %v", err)
}
return conn, nil
}

@ -1,239 +0,0 @@
package lndclient
import (
"context"
"fmt"
"sync"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/lightninglabs/loop/swap"
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/lnrpc/chainrpc"
"google.golang.org/grpc"
)
// ChainNotifierClient exposes base lightning functionality.
type ChainNotifierClient interface {
RegisterBlockEpochNtfn(ctx context.Context) (
chan int32, chan error, error)
RegisterConfirmationsNtfn(ctx context.Context, txid *chainhash.Hash,
pkScript []byte, numConfs, heightHint int32) (
chan *chainntnfs.TxConfirmation, chan error, error)
RegisterSpendNtfn(ctx context.Context,
outpoint *wire.OutPoint, pkScript []byte, heightHint int32) (
chan *chainntnfs.SpendDetail, chan error, error)
}
type chainNotifierClient struct {
client chainrpc.ChainNotifierClient
chainMac serializedMacaroon
wg sync.WaitGroup
}
func newChainNotifierClient(conn *grpc.ClientConn, chainMac serializedMacaroon) *chainNotifierClient {
return &chainNotifierClient{
client: chainrpc.NewChainNotifierClient(conn),
chainMac: chainMac,
}
}
func (s *chainNotifierClient) WaitForFinished() {
s.wg.Wait()
}
func (s *chainNotifierClient) RegisterSpendNtfn(ctx context.Context,
outpoint *wire.OutPoint, pkScript []byte, heightHint int32) (
chan *chainntnfs.SpendDetail, chan error, error) {
var rpcOutpoint *chainrpc.Outpoint
if outpoint != nil {
rpcOutpoint = &chainrpc.Outpoint{
Hash: outpoint.Hash[:],
Index: outpoint.Index,
}
}
macaroonAuth := s.chainMac.WithMacaroonAuth(ctx)
resp, err := s.client.RegisterSpendNtfn(macaroonAuth, &chainrpc.SpendRequest{
HeightHint: uint32(heightHint),
Outpoint: rpcOutpoint,
Script: pkScript,
})
if err != nil {
return nil, nil, err
}
spendChan := make(chan *chainntnfs.SpendDetail, 1)
errChan := make(chan error, 1)
processSpendDetail := func(d *chainrpc.SpendDetails) error {
outpointHash, err := chainhash.NewHash(d.SpendingOutpoint.Hash)
if err != nil {
return err
}
txHash, err := chainhash.NewHash(d.SpendingTxHash)
if err != nil {
return err
}
tx, err := swap.DecodeTx(d.RawSpendingTx)
if err != nil {
return err
}
spendChan <- &chainntnfs.SpendDetail{
SpentOutPoint: &wire.OutPoint{
Hash: *outpointHash,
Index: d.SpendingOutpoint.Index,
},
SpenderTxHash: txHash,
SpenderInputIndex: d.SpendingInputIndex,
SpendingTx: tx,
SpendingHeight: int32(d.SpendingHeight),
}
return nil
}
s.wg.Add(1)
go func() {
defer s.wg.Done()
for {
spendEvent, err := resp.Recv()
if err != nil {
errChan <- err
return
}
switch c := spendEvent.Event.(type) {
case *chainrpc.SpendEvent_Spend:
err := processSpendDetail(c.Spend)
if err != nil {
errChan <- err
}
return
}
}
}()
return spendChan, errChan, nil
}
func (s *chainNotifierClient) RegisterConfirmationsNtfn(ctx context.Context,
txid *chainhash.Hash, pkScript []byte, numConfs, heightHint int32) (
chan *chainntnfs.TxConfirmation, chan error, error) {
var txidSlice []byte
if txid != nil {
txidSlice = txid[:]
}
confStream, err := s.client.RegisterConfirmationsNtfn(
s.chainMac.WithMacaroonAuth(ctx),
&chainrpc.ConfRequest{
Script: pkScript,
NumConfs: uint32(numConfs),
HeightHint: uint32(heightHint),
Txid: txidSlice,
},
)
if err != nil {
return nil, nil, err
}
confChan := make(chan *chainntnfs.TxConfirmation, 1)
errChan := make(chan error, 1)
s.wg.Add(1)
go func() {
defer s.wg.Done()
for {
var confEvent *chainrpc.ConfEvent
confEvent, err := confStream.Recv()
if err != nil {
errChan <- err
return
}
switch c := confEvent.Event.(type) {
// Script confirmed
case *chainrpc.ConfEvent_Conf:
tx, err := swap.DecodeTx(c.Conf.RawTx)
if err != nil {
errChan <- err
return
}
blockHash, err := chainhash.NewHash(
c.Conf.BlockHash,
)
if err != nil {
errChan <- err
return
}
confChan <- &chainntnfs.TxConfirmation{
BlockHeight: c.Conf.BlockHeight,
BlockHash: blockHash,
Tx: tx,
TxIndex: c.Conf.TxIndex,
}
return
// Ignore reorg events, not supported.
case *chainrpc.ConfEvent_Reorg:
continue
// Nil event, should never happen.
case nil:
errChan <- fmt.Errorf("conf event empty")
return
// Unexpected type.
default:
errChan <- fmt.Errorf(
"conf event has unexpected type",
)
return
}
}
}()
return confChan, errChan, nil
}
func (s *chainNotifierClient) RegisterBlockEpochNtfn(ctx context.Context) (
chan int32, chan error, error) {
blockEpochClient, err := s.client.RegisterBlockEpochNtfn(
s.chainMac.WithMacaroonAuth(ctx), &chainrpc.BlockEpoch{},
)
if err != nil {
return nil, nil, err
}
blockErrorChan := make(chan error, 1)
blockEpochChan := make(chan int32)
// Start block epoch goroutine.
s.wg.Add(1)
go func() {
defer s.wg.Done()
for {
epoch, err := blockEpochClient.Recv()
if err != nil {
blockErrorChan <- err
return
}
select {
case blockEpochChan <- int32(epoch.Height):
case <-ctx.Done():
return
}
}
}()
return blockEpochChan, blockErrorChan, nil
}

@ -1,166 +0,0 @@
package lndclient
import (
"context"
"errors"
"sync"
"github.com/btcsuite/btcutil"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnrpc/invoicesrpc"
"github.com/lightningnetwork/lnd/lntypes"
"google.golang.org/grpc"
)
// InvoicesClient exposes invoice functionality.
type InvoicesClient interface {
SubscribeSingleInvoice(ctx context.Context, hash lntypes.Hash) (
<-chan InvoiceUpdate, <-chan error, error)
SettleInvoice(ctx context.Context, preimage lntypes.Preimage) error
CancelInvoice(ctx context.Context, hash lntypes.Hash) error
AddHoldInvoice(ctx context.Context, in *invoicesrpc.AddInvoiceData) (
string, error)
}
// InvoiceUpdate contains a state update for an invoice.
type InvoiceUpdate struct {
State channeldb.ContractState
AmtPaid btcutil.Amount
}
type invoicesClient struct {
client invoicesrpc.InvoicesClient
invoiceMac serializedMacaroon
wg sync.WaitGroup
}
func newInvoicesClient(conn *grpc.ClientConn, invoiceMac serializedMacaroon) *invoicesClient {
return &invoicesClient{
client: invoicesrpc.NewInvoicesClient(conn),
invoiceMac: invoiceMac,
}
}
func (s *invoicesClient) WaitForFinished() {
s.wg.Wait()
}
func (s *invoicesClient) SettleInvoice(ctx context.Context,
preimage lntypes.Preimage) error {
timeoutCtx, cancel := context.WithTimeout(ctx, rpcTimeout)
defer cancel()
rpcCtx := s.invoiceMac.WithMacaroonAuth(timeoutCtx)
_, err := s.client.SettleInvoice(rpcCtx, &invoicesrpc.SettleInvoiceMsg{
Preimage: preimage[:],
})
return err
}
func (s *invoicesClient) CancelInvoice(ctx context.Context,
hash lntypes.Hash) error {
rpcCtx, cancel := context.WithTimeout(ctx, rpcTimeout)
defer cancel()
rpcCtx = s.invoiceMac.WithMacaroonAuth(rpcCtx)
_, err := s.client.CancelInvoice(rpcCtx, &invoicesrpc.CancelInvoiceMsg{
PaymentHash: hash[:],
})
return err
}
func (s *invoicesClient) SubscribeSingleInvoice(ctx context.Context,
hash lntypes.Hash) (<-chan InvoiceUpdate,
<-chan error, error) {
invoiceStream, err := s.client.SubscribeSingleInvoice(
s.invoiceMac.WithMacaroonAuth(ctx),
&invoicesrpc.SubscribeSingleInvoiceRequest{
RHash: hash[:],
},
)
if err != nil {
return nil, nil, err
}
updateChan := make(chan InvoiceUpdate)
errChan := make(chan error, 1)
// Invoice updates goroutine.
s.wg.Add(1)
go func() {
defer s.wg.Done()
for {
invoice, err := invoiceStream.Recv()
if err != nil {
errChan <- err
return
}
state, err := fromRPCInvoiceState(invoice.State)
if err != nil {
errChan <- err
return
}
select {
case updateChan <- InvoiceUpdate{
State: state,
AmtPaid: btcutil.Amount(invoice.AmtPaidSat),
}:
case <-ctx.Done():
return
}
}
}()
return updateChan, errChan, nil
}
func (s *invoicesClient) AddHoldInvoice(ctx context.Context,
in *invoicesrpc.AddInvoiceData) (string, error) {
rpcCtx, cancel := context.WithTimeout(ctx, rpcTimeout)
defer cancel()
rpcIn := &invoicesrpc.AddHoldInvoiceRequest{
Memo: in.Memo,
Hash: in.Hash[:],
Value: int64(in.Value.ToSatoshis()),
Expiry: in.Expiry,
CltvExpiry: in.CltvExpiry,
Private: true,
}
rpcCtx = s.invoiceMac.WithMacaroonAuth(rpcCtx)
resp, err := s.client.AddHoldInvoice(rpcCtx, rpcIn)
if err != nil {
return "", err
}
return resp.PaymentRequest, nil
}
func fromRPCInvoiceState(state lnrpc.Invoice_InvoiceState) (
channeldb.ContractState, error) {
switch state {
case lnrpc.Invoice_OPEN:
return channeldb.ContractOpen, nil
case lnrpc.Invoice_ACCEPTED:
return channeldb.ContractAccepted, nil
case lnrpc.Invoice_SETTLED:
return channeldb.ContractSettled, nil
case lnrpc.Invoice_CANCELED:
return channeldb.ContractCanceled, nil
}
return 0, errors.New("unknown state")
}

File diff suppressed because it is too large Load Diff

@ -1,463 +0,0 @@
package lndclient
import (
"context"
"errors"
"fmt"
"net"
"path/filepath"
"time"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcutil"
"github.com/lightninglabs/loop/swap"
"github.com/lightningnetwork/lnd/lncfg"
"github.com/lightningnetwork/lnd/lnrpc/verrpc"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/status"
)
var (
rpcTimeout = 30 * time.Second
// minimalCompatibleVersion is the minimum version and build tags
// required in lnd to get all functionality implemented in lndclient.
// Users can provide their own, specific version if needed. If only a
// subset of the lndclient functionality is needed, the required build
// tags can be adjusted accordingly. This default will be used as a fall
// back version if none is specified in the configuration.
minimalCompatibleVersion = &verrpc.Version{
AppMajor: 0,
AppMinor: 10,
AppPatch: 1,
BuildTags: []string{
"signrpc", "walletrpc", "chainrpc", "invoicesrpc",
},
}
// ErrVersionCheckNotImplemented is the error that is returned if the
// version RPC is not implemented in lnd. This means the version of lnd
// is lower than v0.10.0-beta.
ErrVersionCheckNotImplemented = errors.New("version check not " +
"implemented, need minimum lnd version of v0.10.0-beta")
// ErrVersionIncompatible is the error that is returned if the connected
// lnd instance is not supported.
ErrVersionIncompatible = errors.New("version incompatible")
// ErrBuildTagsMissing is the error that is returned if the
// connected lnd instance does not have all built tags activated that
// are required.
ErrBuildTagsMissing = errors.New("build tags missing")
)
// LndServicesConfig holds all configuration settings that are needed to connect
// to an lnd node.
type LndServicesConfig struct {
// LndAddress is the network address (host:port) of the lnd node to
// connect to.
LndAddress string
// Network is the bitcoin network we expect the lnd node to operate on.
Network string
// MacaroonDir is the directory where all lnd macaroons can be found.
MacaroonDir string
// TLSPath is the path to lnd's TLS certificate file.
TLSPath string
// CheckVersion is the minimum version the connected lnd node needs to
// be in order to be compatible. The node will be checked against this
// when connecting. If no version is supplied, the default minimum
// version will be used.
CheckVersion *verrpc.Version
// Dialer is an optional dial function that can be passed in if the
// default lncfg.ClientAddressDialer should not be used.
Dialer DialerFunc
}
// DialerFunc is a function that is used as grpc.WithContextDialer().
type DialerFunc func(context.Context, string) (net.Conn, error)
// LndServices constitutes a set of required services.
type LndServices struct {
Client LightningClient
WalletKit WalletKitClient
ChainNotifier ChainNotifierClient
Signer SignerClient
Invoices InvoicesClient
Router RouterClient
Versioner VersionerClient
ChainParams *chaincfg.Params
NodeAlias string
NodePubkey [33]byte
Version *verrpc.Version
macaroons *macaroonPouch
}
// GrpcLndServices constitutes a set of required RPC services.
type GrpcLndServices struct {
LndServices
cleanup func()
}
// NewLndServices creates creates a connection to the given lnd instance and
// creates a set of required RPC services.
func NewLndServices(cfg *LndServicesConfig) (*GrpcLndServices, error) {
// We need to use a custom dialer so we can also connect to unix
// sockets and not just TCP addresses.
if cfg.Dialer == nil {
cfg.Dialer = lncfg.ClientAddressDialer(defaultRPCPort)
}
// Fall back to minimal compatible version if none if specified.
if cfg.CheckVersion == nil {
cfg.CheckVersion = minimalCompatibleVersion
}
// Based on the network, if the macaroon directory isn't set, then
// we'll use the expected default locations.
macaroonDir := cfg.MacaroonDir
if macaroonDir == "" {
switch cfg.Network {
case "testnet":
macaroonDir = filepath.Join(
defaultLndDir, defaultDataDir,
defaultChainSubDir, "bitcoin", "testnet",
)
case "mainnet":
macaroonDir = filepath.Join(
defaultLndDir, defaultDataDir,
defaultChainSubDir, "bitcoin", "mainnet",
)
case "simnet":
macaroonDir = filepath.Join(
defaultLndDir, defaultDataDir,
defaultChainSubDir, "bitcoin", "simnet",
)
case "regtest":
macaroonDir = filepath.Join(
defaultLndDir, defaultDataDir,
defaultChainSubDir, "bitcoin", "regtest",
)
default:
return nil, fmt.Errorf("unsupported network: %v",
cfg.Network)
}
}
// Setup connection with lnd
log.Infof("Creating lnd connection to %v", cfg.LndAddress)
conn, err := getClientConn(cfg)
if err != nil {
return nil, err
}
log.Infof("Connected to lnd")
chainParams, err := swap.ChainParamsFromNetwork(cfg.Network)
if err != nil {
return nil, err
}
// We are going to check that the connected lnd is on the same network
// and is a compatible version with all the required subservers enabled.
// For this, we make two calls, both of which only need the readonly
// macaroon. We don't use the pouch yet because if not all subservers
// are enabled, then not all macaroons might be there and the user would
// get a more cryptic error message.
readonlyMac, err := newSerializedMacaroon(
filepath.Join(macaroonDir, defaultReadonlyFilename),
)
if err != nil {
return nil, err
}
nodeAlias, nodeKey, version, err := checkLndCompatibility(
conn, chainParams, readonlyMac, cfg.Network, cfg.CheckVersion,
)
if err != nil {
return nil, err
}
// Now that we've ensured our macaroon directory is set properly, we
// can retrieve our full macaroon pouch from the directory.
macaroons, err := newMacaroonPouch(macaroonDir)
if err != nil {
return nil, fmt.Errorf("unable to obtain macaroons: %v", err)
}
// With the macaroons loaded and the version checked, we can now create
// the real lightning client which uses the admin macaroon.
lightningClient := newLightningClient(
conn, chainParams, macaroons.adminMac,
)
// With the network check passed, we'll now initialize the rest of the
// sub-server connections, giving each of them their specific macaroon.
notifierClient := newChainNotifierClient(conn, macaroons.chainMac)
signerClient := newSignerClient(conn, macaroons.signerMac)
walletKitClient := newWalletKitClient(conn, macaroons.walletKitMac)
invoicesClient := newInvoicesClient(conn, macaroons.invoiceMac)
routerClient := newRouterClient(conn, macaroons.routerMac)
versionerClient := newVersionerClient(conn, macaroons.readonlyMac)
cleanup := func() {
log.Debugf("Closing lnd connection")
err := conn.Close()
if err != nil {
log.Errorf("Error closing client connection: %v", err)
}
log.Debugf("Wait for client to finish")
lightningClient.WaitForFinished()
log.Debugf("Wait for chain notifier to finish")
notifierClient.WaitForFinished()
log.Debugf("Wait for invoices to finish")
invoicesClient.WaitForFinished()
log.Debugf("Lnd services finished")
}
services := &GrpcLndServices{
LndServices: LndServices{
Client: lightningClient,
WalletKit: walletKitClient,
ChainNotifier: notifierClient,
Signer: signerClient,
Invoices: invoicesClient,
Router: routerClient,
Versioner: versionerClient,
ChainParams: chainParams,
NodeAlias: nodeAlias,
NodePubkey: nodeKey,
Version: version,
macaroons: macaroons,
},
cleanup: cleanup,
}
log.Infof("Using network %v", cfg.Network)
return services, nil
}
// Close closes the lnd connection and waits for all sub server clients to
// finish their goroutines.
func (s *GrpcLndServices) Close() {
s.cleanup()
log.Debugf("Lnd services finished")
}
// checkLndCompatibility makes sure the connected lnd instance is running on the
// correct network, has the version RPC implemented, is the correct minimal
// version and supports all required build tags/subservers.
func checkLndCompatibility(conn *grpc.ClientConn, chainParams *chaincfg.Params,
readonlyMac serializedMacaroon, network string,
minVersion *verrpc.Version) (string, [33]byte, *verrpc.Version, error) {
// onErr is a closure that simplifies returning multiple values in the
// error case.
onErr := func(err error) (string, [33]byte, *verrpc.Version, error) {
closeErr := conn.Close()
if closeErr != nil {
log.Errorf("Error closing lnd connection: %v", closeErr)
}
// Make static error messages a bit less cryptic by adding the
// version or build tag that we expect.
newErr := fmt.Errorf("lnd compatibility check failed: %v", err)
if err == ErrVersionIncompatible || err == ErrBuildTagsMissing {
newErr = fmt.Errorf("error checking connected lnd "+
"version. at least version \"%s\" is "+
"required", VersionString(minVersion))
}
return "", [33]byte{}, nil, newErr
}
// We use our own clients with a readonly macaroon here, because we know
// that's all we need for the checks.
lightningClient := newLightningClient(conn, chainParams, readonlyMac)
versionerClient := newVersionerClient(conn, readonlyMac)
// With our readonly macaroon obtained, we'll ensure that the network
// for lnd matches our expected network.
info, err := lightningClient.GetInfo(context.Background())
if err != nil {
err := fmt.Errorf("unable to get info for lnd node: %v", err)
return onErr(err)
}
if network != info.Network {
err := fmt.Errorf("network mismatch with connected lnd node, "+
"wanted '%s', got '%s'", network, info.Network)
return onErr(err)
}
// Now let's also check the version of the connected lnd node.
version, err := checkVersionCompatibility(versionerClient, minVersion)
if err != nil {
return onErr(err)
}
// Return the static part of the info we just queried from the node so
// it can be cached for later use.
return info.Alias, info.IdentityPubkey, version, nil
}
// checkVersionCompatibility makes sure the connected lnd node has the correct
// version and required build tags enabled.
//
// NOTE: This check will **never** return a non-nil error for a version of
// lnd < 0.10.0 because any version previous to 0.10.0 doesn't have the version
// endpoint implemented!
func checkVersionCompatibility(client VersionerClient,
expected *verrpc.Version) (*verrpc.Version, error) {
// First, test that the version RPC is even implemented.
version, err := client.GetVersion(context.Background())
if err != nil {
// The version service has only been added in lnd v0.10.0. If
// we get an unimplemented error, it means the lnd version is
// definitely older than that.
s, ok := status.FromError(err)
if ok && s.Code() == codes.Unimplemented {
return nil, ErrVersionCheckNotImplemented
}
return nil, fmt.Errorf("GetVersion error: %v", err)
}
log.Infof("lnd version: %v", VersionString(version))
// Now check the version and make sure all required build tags are set.
err = assertVersionCompatible(version, expected)
if err != nil {
return nil, err
}
err = assertBuildTagsEnabled(version, expected.BuildTags)
if err != nil {
return nil, err
}
// All check positive, version is fully compatible.
return version, nil
}
// assertVersionCompatible makes sure the detected lnd version is compatible
// with our current version requirements.
func assertVersionCompatible(actual *verrpc.Version,
expected *verrpc.Version) error {
// We need to check the versions parts sequentially as they are
// hierarchical.
if actual.AppMajor != expected.AppMajor {
if actual.AppMajor > expected.AppMajor {
return nil
}
return ErrVersionIncompatible
}
if actual.AppMinor != expected.AppMinor {
if actual.AppMinor > expected.AppMinor {
return nil
}
return ErrVersionIncompatible
}
if actual.AppPatch != expected.AppPatch {
if actual.AppPatch > expected.AppPatch {
return nil
}
return ErrVersionIncompatible
}
// The actual version and expected version are identical.
return nil
}
// assertBuildTagsEnabled makes sure all required build tags are set.
func assertBuildTagsEnabled(actual *verrpc.Version,
requiredTags []string) error {
tagMap := make(map[string]struct{})
for _, tag := range actual.BuildTags {
tagMap[tag] = struct{}{}
}
for _, required := range requiredTags {
if _, ok := tagMap[required]; !ok {
return ErrBuildTagsMissing
}
}
// All tags found.
return nil
}
var (
defaultRPCPort = "10009"
defaultLndDir = btcutil.AppDataDir("lnd", false)
defaultTLSCertFilename = "tls.cert"
defaultTLSCertPath = filepath.Join(
defaultLndDir, defaultTLSCertFilename,
)
defaultDataDir = "data"
defaultChainSubDir = "chain"
defaultAdminMacaroonFilename = "admin.macaroon"
defaultInvoiceMacaroonFilename = "invoices.macaroon"
defaultChainMacaroonFilename = "chainnotifier.macaroon"
defaultWalletKitMacaroonFilename = "walletkit.macaroon"
defaultRouterMacaroonFilename = "router.macaroon"
defaultSignerFilename = "signer.macaroon"
defaultReadonlyFilename = "readonly.macaroon"
// maxMsgRecvSize is the largest gRPC message our client will receive.
// We set this to 200MiB.
maxMsgRecvSize = grpc.MaxCallRecvMsgSize(1 * 1024 * 1024 * 200)
)
func getClientConn(cfg *LndServicesConfig) (*grpc.ClientConn, error) {
// Load the specified TLS certificate and build transport credentials
// with it.
tlsPath := cfg.TLSPath
if tlsPath == "" {
tlsPath = defaultTLSCertPath
}
creds, err := credentials.NewClientTLSFromFile(tlsPath, "")
if err != nil {
return nil, err
}
// Create a dial options array.
opts := []grpc.DialOption{
grpc.WithTransportCredentials(creds),
// Use a custom dialer, to allow connections to unix sockets,
// in-memory listeners etc, and not just TCP addresses.
grpc.WithContextDialer(cfg.Dialer),
grpc.WithDefaultCallOptions(maxMsgRecvSize),
}
conn, err := grpc.Dial(cfg.LndAddress, opts...)
if err != nil {
return nil, fmt.Errorf("unable to connect to RPC server: %v",
err)
}
return conn, nil
}

@ -1,158 +0,0 @@
package lndclient
import (
"context"
"testing"
"github.com/lightningnetwork/lnd/lnrpc/verrpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
type mockVersioner struct {
version *verrpc.Version
err error
}
func (m *mockVersioner) GetVersion(_ context.Context) (*verrpc.Version, error) {
return m.version, m.err
}
// TestCheckVersionCompatibility makes sure the correct error is returned if an
// old lnd is connected that doesn't implement the version RPC, has an older
// version or if an lnd with not all subservers enabled is connected.
func TestCheckVersionCompatibility(t *testing.T) {
// Make sure a version check against a node that doesn't implement the
// version RPC always fails.
unimplemented := &mockVersioner{
err: status.Error(codes.Unimplemented, "missing"),
}
_, err := checkVersionCompatibility(unimplemented, &verrpc.Version{
AppMajor: 0,
AppMinor: 10,
AppPatch: 0,
})
if err != ErrVersionCheckNotImplemented {
t.Fatalf("unexpected error. got '%v' wanted '%v'", err,
ErrVersionCheckNotImplemented)
}
// Next, make sure an older version than what we want is rejected.
oldVersion := &mockVersioner{
version: &verrpc.Version{
AppMajor: 0,
AppMinor: 10,
AppPatch: 0,
},
}
_, err = checkVersionCompatibility(oldVersion, &verrpc.Version{
AppMajor: 0,
AppMinor: 11,
AppPatch: 0,
})
if err != ErrVersionIncompatible {
t.Fatalf("unexpected error. got '%v' wanted '%v'", err,
ErrVersionIncompatible)
}
// Finally, make sure we also get the correct error when trying to run
// against an lnd that doesn't have all required build tags enabled.
buildTagsMissing := &mockVersioner{
version: &verrpc.Version{
AppMajor: 0,
AppMinor: 10,
AppPatch: 0,
BuildTags: []string{"dev", "lntest", "btcd", "signrpc"},
},
}
_, err = checkVersionCompatibility(buildTagsMissing, &verrpc.Version{
AppMajor: 0,
AppMinor: 10,
AppPatch: 0,
BuildTags: []string{"signrpc", "walletrpc"},
})
if err != ErrBuildTagsMissing {
t.Fatalf("unexpected error. got '%v' wanted '%v'", err,
ErrVersionIncompatible)
}
}
// TestLndVersionCheckComparison makes sure the version check comparison works
// correctly and considers all three version levels.
func TestLndVersionCheckComparison(t *testing.T) {
actual := &verrpc.Version{
AppMajor: 1,
AppMinor: 2,
AppPatch: 3,
}
testCases := []struct {
name string
expectMajor uint32
expectMinor uint32
expectPatch uint32
actual *verrpc.Version
expectedErr error
}{
{
name: "no expectation",
expectMajor: 0,
expectMinor: 0,
expectPatch: 0,
actual: actual,
expectedErr: nil,
},
{
name: "expect exact same version",
expectMajor: 1,
expectMinor: 2,
expectPatch: 3,
actual: actual,
expectedErr: nil,
},
{
name: "ignore patch if minor is bigger",
expectMajor: 12,
expectMinor: 9,
expectPatch: 14,
actual: &verrpc.Version{
AppMajor: 12,
AppMinor: 22,
AppPatch: 0,
},
expectedErr: nil,
},
{
name: "all fields different",
expectMajor: 3,
expectMinor: 2,
expectPatch: 1,
actual: actual,
expectedErr: ErrVersionIncompatible,
},
{
name: "patch version different",
expectMajor: 1,
expectMinor: 2,
expectPatch: 4,
actual: actual,
expectedErr: ErrVersionIncompatible,
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
err := assertVersionCompatible(
tc.actual, &verrpc.Version{
AppMajor: tc.expectMajor,
AppMinor: tc.expectMinor,
AppPatch: tc.expectPatch,
},
)
if err != tc.expectedErr {
t.Fatalf("unexpected error, got '%v' wanted "+
"'%v'", err, tc.expectedErr)
}
})
}
}

@ -1,23 +0,0 @@
package lndclient
import (
"github.com/btcsuite/btclog"
"github.com/lightningnetwork/lnd/build"
)
// log is a logger that is initialized with no output filters. This
// means the package will not perform any logging by default until the
// caller requests it.
var log btclog.Logger
// The default amount of logging is none.
func init() {
UseLogger(build.NewSubLogger("LNDC", nil))
}
// UseLogger uses a specified Logger to output package logging info.
// This should be used in preference to SetLogWriter if the caller is also
// using btclog.
func UseLogger(logger btclog.Logger) {
log = logger
}

@ -1,118 +0,0 @@
package lndclient
import (
"context"
"encoding/hex"
"io/ioutil"
"path/filepath"
"google.golang.org/grpc/metadata"
)
// serializedMacaroon is a type that represents a hex-encoded macaroon. We'll
// use this primarily vs the raw binary format as the gRPC metadata feature
// requires that all keys and values be strings.
type serializedMacaroon string
// newSerializedMacaroon reads a new serializedMacaroon from that target
// macaroon path. If the file can't be found, then an error is returned.
func newSerializedMacaroon(macaroonPath string) (serializedMacaroon, error) {
macBytes, err := ioutil.ReadFile(macaroonPath)
if err != nil {
return "", err
}
return serializedMacaroon(hex.EncodeToString(macBytes)), nil
}
// WithMacaroonAuth modifies the passed context to include the macaroon KV
// metadata of the target macaroon. This method can be used to add the macaroon
// at call time, rather than when the connection to the gRPC server is created.
func (s serializedMacaroon) WithMacaroonAuth(ctx context.Context) context.Context {
return metadata.AppendToOutgoingContext(ctx, "macaroon", string(s))
}
// macaroonPouch holds the set of macaroons we need to interact with lnd for
// Loop. Each sub-server has its own macaroon, and for the remaining temporary
// calls that directly hit lnd, we'll use the admin macaroon.
type macaroonPouch struct {
// invoiceMac is the macaroon for the invoices sub-server.
invoiceMac serializedMacaroon
// chainMac is the macaroon for the ChainNotifier sub-server.
chainMac serializedMacaroon
// signerMac is the macaroon for the Signer sub-server.
signerMac serializedMacaroon
// walletKitMac is the macaroon for the WalletKit sub-server.
walletKitMac serializedMacaroon
// routerMac is the macaroon for the router sub-server.
routerMac serializedMacaroon
// adminMac is the primary admin macaroon for lnd.
adminMac serializedMacaroon
// readonlyMac is the primary read-only macaroon for lnd.
readonlyMac serializedMacaroon
}
// newMacaroonPouch returns a new instance of a fully populated macaroonPouch
// given the directory where all the macaroons are stored.
func newMacaroonPouch(macaroonDir string) (*macaroonPouch, error) {
m := &macaroonPouch{}
var err error
m.invoiceMac, err = newSerializedMacaroon(
filepath.Join(macaroonDir, defaultInvoiceMacaroonFilename),
)
if err != nil {
return nil, err
}
m.chainMac, err = newSerializedMacaroon(
filepath.Join(macaroonDir, defaultChainMacaroonFilename),
)
if err != nil {
return nil, err
}
m.signerMac, err = newSerializedMacaroon(
filepath.Join(macaroonDir, defaultSignerFilename),
)
if err != nil {
return nil, err
}
m.walletKitMac, err = newSerializedMacaroon(
filepath.Join(macaroonDir, defaultWalletKitMacaroonFilename),
)
if err != nil {
return nil, err
}
m.routerMac, err = newSerializedMacaroon(
filepath.Join(macaroonDir, defaultRouterMacaroonFilename),
)
if err != nil {
return nil, err
}
m.adminMac, err = newSerializedMacaroon(
filepath.Join(macaroonDir, defaultAdminMacaroonFilename),
)
if err != nil {
return nil, err
}
m.readonlyMac, err = newSerializedMacaroon(
filepath.Join(macaroonDir, defaultReadonlyFilename),
)
if err != nil {
return nil, err
}
return m, nil
}

@ -1,416 +0,0 @@
package lndclient
import (
"context"
"crypto/rand"
"encoding/hex"
"fmt"
"io"
"time"
"github.com/btcsuite/btcutil"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/record"
"github.com/lightningnetwork/lnd/routing/route"
"github.com/lightningnetwork/lnd/zpay32"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
// RouterClient exposes payment functionality.
type RouterClient interface {
// SendPayment attempts to route a payment to the final destination. The
// call returns a payment update stream and an error stream.
SendPayment(ctx context.Context, request SendPaymentRequest) (
chan PaymentStatus, chan error, error)
// TrackPayment picks up a previously started payment and returns a
// payment update stream and an error stream.
TrackPayment(ctx context.Context, hash lntypes.Hash) (
chan PaymentStatus, chan error, error)
}
// PaymentStatus describe the state of a payment.
type PaymentStatus struct {
State lnrpc.Payment_PaymentStatus
// FailureReason is the reason why the payment failed. Only set when
// State is Failed.
FailureReason lnrpc.PaymentFailureReason
Preimage lntypes.Preimage
Fee lnwire.MilliSatoshi
Value lnwire.MilliSatoshi
InFlightAmt lnwire.MilliSatoshi
InFlightHtlcs int
}
func (p PaymentStatus) String() string {
text := fmt.Sprintf("state=%v", p.State)
if p.State == lnrpc.Payment_IN_FLIGHT {
text += fmt.Sprintf(", inflight_htlcs=%v, inflight_amt=%v",
p.InFlightHtlcs, p.InFlightAmt)
}
return text
}
// SendPaymentRequest defines the payment parameters for a new payment.
type SendPaymentRequest struct {
// Invoice is an encoded payment request. The individual payment
// parameters Target, Amount, PaymentHash, FinalCLTVDelta and RouteHints
// are only processed when the Invoice field is empty.
Invoice string
// MaxFee is the fee limit for this payment.
MaxFee btcutil.Amount
// MaxCltv is the maximum timelock for this payment. If nil, there is no
// maximum.
MaxCltv *int32
// OutgoingChanIds is a restriction on the set of possible outgoing
// channels. If nil or empty, there is no restriction.
OutgoingChanIds []uint64
// Timeout is the payment loop timeout. After this time, no new payment
// attempts will be started.
Timeout time.Duration
// Target is the node in which the payment should be routed towards.
Target route.Vertex
// Amount is the value of the payment to send through the network in
// satoshis.
Amount btcutil.Amount
// PaymentHash is the r-hash value to use within the HTLC extended to
// the first hop.
PaymentHash *lntypes.Hash
// FinalCLTVDelta is the CTLV expiry delta to use for the _final_ hop
// in the route. This means that the final hop will have a CLTV delta
// of at least: currentHeight + FinalCLTVDelta.
FinalCLTVDelta uint16
// RouteHints represents the different routing hints that can be used to
// assist a payment in reaching its destination successfully. These
// hints will act as intermediate hops along the route.
//
// NOTE: This is optional unless required by the payment. When providing
// multiple routes, ensure the hop hints within each route are chained
// together and sorted in forward order in order to reach the
// destination successfully.
RouteHints [][]zpay32.HopHint
// LastHopPubkey is the pubkey of the last hop of the route taken
// for this payment. If empty, any hop may be used.
LastHopPubkey *route.Vertex
// MaxParts is the maximum number of partial payments that may be used
// to complete the full amount.
MaxParts uint32
// KeySend is set to true if the tlv payload will include the preimage.
KeySend bool
// CustomRecords holds the custom TLV records that will be added to the
// payment.
CustomRecords map[uint64][]byte
}
// routerClient is a wrapper around the generated routerrpc proxy.
type routerClient struct {
client routerrpc.RouterClient
routerKitMac serializedMacaroon
}
func newRouterClient(conn *grpc.ClientConn,
routerKitMac serializedMacaroon) *routerClient {
return &routerClient{
client: routerrpc.NewRouterClient(conn),
routerKitMac: routerKitMac,
}
}
// SendPayment attempts to route a payment to the final destination. The call
// returns a payment update stream and an error stream.
func (r *routerClient) SendPayment(ctx context.Context,
request SendPaymentRequest) (chan PaymentStatus, chan error, error) {
rpcCtx := r.routerKitMac.WithMacaroonAuth(ctx)
rpcReq := &routerrpc.SendPaymentRequest{
FeeLimitSat: int64(request.MaxFee),
PaymentRequest: request.Invoice,
TimeoutSeconds: int32(request.Timeout.Seconds()),
MaxParts: request.MaxParts,
OutgoingChanIds: request.OutgoingChanIds,
}
if request.MaxCltv != nil {
rpcReq.CltvLimit = *request.MaxCltv
}
if request.LastHopPubkey != nil {
rpcReq.LastHopPubkey = request.LastHopPubkey[:]
}
rpcReq.DestCustomRecords = request.CustomRecords
if request.KeySend {
if request.PaymentHash != nil {
return nil, nil, fmt.Errorf(
"keysend payment must not include a preset payment hash")
}
var preimage lntypes.Preimage
if _, err := rand.Read(preimage[:]); err != nil {
return nil, nil, err
}
if rpcReq.DestCustomRecords == nil {
rpcReq.DestCustomRecords = make(map[uint64][]byte)
}
// Override the payment hash.
rpcReq.DestCustomRecords[record.KeySendType] = preimage[:]
hash := preimage.Hash()
request.PaymentHash = &hash
}
// Only if there is no payment request set, we will parse the individual
// payment parameters.
if request.Invoice == "" {
rpcReq.Dest = request.Target[:]
rpcReq.Amt = int64(request.Amount)
rpcReq.PaymentHash = request.PaymentHash[:]
rpcReq.FinalCltvDelta = int32(request.FinalCLTVDelta)
routeHints, err := marshallRouteHints(request.RouteHints)
if err != nil {
return nil, nil, err
}
rpcReq.RouteHints = routeHints
}
stream, err := r.client.SendPaymentV2(rpcCtx, rpcReq)
if err != nil {
return nil, nil, err
}
return r.trackPayment(ctx, stream)
}
// TrackPayment picks up a previously started payment and returns a payment
// update stream and an error stream.
func (r *routerClient) TrackPayment(ctx context.Context,
hash lntypes.Hash) (chan PaymentStatus, chan error, error) {
ctx = r.routerKitMac.WithMacaroonAuth(ctx)
stream, err := r.client.TrackPaymentV2(
ctx, &routerrpc.TrackPaymentRequest{
PaymentHash: hash[:],
},
)
if err != nil {
return nil, nil, err
}
return r.trackPayment(ctx, stream)
}
// trackPayment takes an update stream from either a SendPayment or a
// TrackPayment rpc call and converts it into distinct update and error streams.
// Once the payment reaches a final state, the status and error channels will
// be closed to signal that we are finished sending into them.
func (r *routerClient) trackPayment(ctx context.Context,
stream routerrpc.Router_TrackPaymentV2Client) (chan PaymentStatus,
chan error, error) {
statusChan := make(chan PaymentStatus)
errorChan := make(chan error, 1)
go func() {
for {
payment, err := stream.Recv()
if err != nil {
// If we get an EOF error, the payment has
// reached a final state and the server is
// finished sending us updates. We close both
// channels to signal that we are done sending
// values on them and return.
if err == io.EOF {
close(statusChan)
close(errorChan)
return
}
switch status.Convert(err).Code() {
// NotFound is only expected as a response to
// TrackPayment.
case codes.NotFound:
err = channeldb.ErrPaymentNotInitiated
// NotFound is only expected as a response to
// SendPayment.
case codes.AlreadyExists:
err = channeldb.ErrAlreadyPaid
}
errorChan <- err
return
}
status, err := unmarshallPaymentStatus(payment)
if err != nil {
errorChan <- err
return
}
select {
case statusChan <- *status:
case <-ctx.Done():
return
}
}
}()
return statusChan, errorChan, nil
}
// unmarshallPaymentStatus converts an rpc status update to the PaymentStatus
// type that is used throughout the application.
func unmarshallPaymentStatus(rpcPayment *lnrpc.Payment) (
*PaymentStatus, error) {
status := PaymentStatus{
State: rpcPayment.Status,
}
switch status.State {
case lnrpc.Payment_SUCCEEDED:
preimage, err := lntypes.MakePreimageFromStr(
rpcPayment.PaymentPreimage,
)
if err != nil {
return nil, err
}
status.Preimage = preimage
status.Fee = lnwire.MilliSatoshi(rpcPayment.FeeMsat)
status.Value = lnwire.MilliSatoshi(rpcPayment.ValueMsat)
case lnrpc.Payment_FAILED:
status.FailureReason = rpcPayment.FailureReason
}
for _, htlc := range rpcPayment.Htlcs {
if htlc.Status != lnrpc.HTLCAttempt_IN_FLIGHT {
continue
}
status.InFlightHtlcs++
lastHop := htlc.Route.Hops[len(htlc.Route.Hops)-1]
status.InFlightAmt += lnwire.MilliSatoshi(
lastHop.AmtToForwardMsat,
)
}
return &status, nil
}
// unmarshallRoute unmarshalls an rpc route.
func unmarshallRoute(rpcroute *lnrpc.Route) (
*route.Route, error) {
hops := make([]*route.Hop, len(rpcroute.Hops))
for i, hop := range rpcroute.Hops {
routeHop, err := unmarshallHop(hop)
if err != nil {
return nil, err
}
hops[i] = routeHop
}
// TODO(joostjager): Fetch self node from lnd.
selfNode := route.Vertex{}
route, err := route.NewRouteFromHops(
lnwire.MilliSatoshi(rpcroute.TotalAmtMsat),
rpcroute.TotalTimeLock,
selfNode,
hops,
)
if err != nil {
return nil, err
}
return route, nil
}
// unmarshallKnownPubkeyHop unmarshalls an rpc hop.
func unmarshallHop(hop *lnrpc.Hop) (*route.Hop, error) {
pubKey, err := hex.DecodeString(hop.PubKey)
if err != nil {
return nil, fmt.Errorf("cannot decode pubkey %s", hop.PubKey)
}
var pubKeyBytes [33]byte
copy(pubKeyBytes[:], pubKey)
return &route.Hop{
OutgoingTimeLock: hop.Expiry,
AmtToForward: lnwire.MilliSatoshi(hop.AmtToForwardMsat),
PubKeyBytes: pubKeyBytes,
ChannelID: hop.ChanId,
}, nil
}
// marshallRouteHints marshalls a list of route hints.
func marshallRouteHints(routeHints [][]zpay32.HopHint) (
[]*lnrpc.RouteHint, error) {
rpcRouteHints := make([]*lnrpc.RouteHint, 0, len(routeHints))
for _, routeHint := range routeHints {
rpcRouteHint := make(
[]*lnrpc.HopHint, 0, len(routeHint),
)
for _, hint := range routeHint {
rpcHint, err := marshallHopHint(hint)
if err != nil {
return nil, err
}
rpcRouteHint = append(rpcRouteHint, rpcHint)
}
rpcRouteHints = append(rpcRouteHints, &lnrpc.RouteHint{
HopHints: rpcRouteHint,
})
}
return rpcRouteHints, nil
}
// marshallHopHint marshalls a single hop hint.
func marshallHopHint(hint zpay32.HopHint) (*lnrpc.HopHint, error) {
nodeID, err := route.NewVertexFromBytes(
hint.NodeID.SerializeCompressed(),
)
if err != nil {
return nil, err
}
return &lnrpc.HopHint{
ChanId: hint.ChannelID,
CltvExpiryDelta: uint32(hint.CLTVExpiryDelta),
FeeBaseMsat: hint.FeeBaseMSat,
FeeProportionalMillionths: hint.FeeProportionalMillionths,
NodeId: nodeID.String(),
}, nil
}

@ -1,254 +0,0 @@
package lndclient
import (
"context"
"github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcd/wire"
"github.com/lightninglabs/loop/swap"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lnrpc/signrpc"
"google.golang.org/grpc"
)
// SignerClient exposes sign functionality.
type SignerClient interface {
SignOutputRaw(ctx context.Context, tx *wire.MsgTx,
signDescriptors []*input.SignDescriptor) ([][]byte, error)
// ComputeInputScript generates the proper input script for P2WPKH
// output and NP2WPKH outputs. This method only requires that the
// `Output`, `HashType`, `SigHashes` and `InputIndex` fields are
// populated within the sign descriptors.
ComputeInputScript(ctx context.Context, tx *wire.MsgTx,
signDescriptors []*input.SignDescriptor) ([]*input.Script, error)
// SignMessage signs a message with the key specified in the key
// locator. The returned signature is fixed-size LN wire format encoded.
SignMessage(ctx context.Context, msg []byte,
locator keychain.KeyLocator) ([]byte, error)
// VerifyMessage verifies a signature over a message using the public
// key provided. The signature must be fixed-size LN wire format
// encoded.
VerifyMessage(ctx context.Context, msg, sig []byte, pubkey [33]byte) (
bool, error)
// DeriveSharedKey returns a shared secret key by performing
// Diffie-Hellman key derivation between the ephemeral public key and
// the key specified by the key locator (or the node's identity private
// key if no key locator is specified):
//
// P_shared = privKeyNode * ephemeralPubkey
//
// The resulting shared public key is serialized in the compressed
// format and hashed with SHA256, resulting in a final key length of 256
// bits.
DeriveSharedKey(ctx context.Context, ephemeralPubKey *btcec.PublicKey,
keyLocator *keychain.KeyLocator) ([32]byte, error)
}
type signerClient struct {
client signrpc.SignerClient
signerMac serializedMacaroon
}
func newSignerClient(conn *grpc.ClientConn,
signerMac serializedMacaroon) *signerClient {
return &signerClient{
client: signrpc.NewSignerClient(conn),
signerMac: signerMac,
}
}
func marshallSignDescriptors(signDescriptors []*input.SignDescriptor,
) []*signrpc.SignDescriptor {
rpcSignDescs := make([]*signrpc.SignDescriptor, len(signDescriptors))
for i, signDesc := range signDescriptors {
var keyBytes []byte
var keyLocator *signrpc.KeyLocator
if signDesc.KeyDesc.PubKey != nil {
keyBytes = signDesc.KeyDesc.PubKey.SerializeCompressed()
} else {
keyLocator = &signrpc.KeyLocator{
KeyFamily: int32(
signDesc.KeyDesc.KeyLocator.Family,
),
KeyIndex: int32(
signDesc.KeyDesc.KeyLocator.Index,
),
}
}
var doubleTweak []byte
if signDesc.DoubleTweak != nil {
doubleTweak = signDesc.DoubleTweak.Serialize()
}
rpcSignDescs[i] = &signrpc.SignDescriptor{
WitnessScript: signDesc.WitnessScript,
Output: &signrpc.TxOut{
PkScript: signDesc.Output.PkScript,
Value: signDesc.Output.Value,
},
Sighash: uint32(signDesc.HashType),
InputIndex: int32(signDesc.InputIndex),
KeyDesc: &signrpc.KeyDescriptor{
RawKeyBytes: keyBytes,
KeyLoc: keyLocator,
},
SingleTweak: signDesc.SingleTweak,
DoubleTweak: doubleTweak,
}
}
return rpcSignDescs
}
func (s *signerClient) SignOutputRaw(ctx context.Context, tx *wire.MsgTx,
signDescriptors []*input.SignDescriptor) ([][]byte, error) {
txRaw, err := swap.EncodeTx(tx)
if err != nil {
return nil, err
}
rpcSignDescs := marshallSignDescriptors(signDescriptors)
rpcCtx, cancel := context.WithTimeout(ctx, rpcTimeout)
defer cancel()
rpcCtx = s.signerMac.WithMacaroonAuth(rpcCtx)
resp, err := s.client.SignOutputRaw(rpcCtx,
&signrpc.SignReq{
RawTxBytes: txRaw,
SignDescs: rpcSignDescs,
},
)
if err != nil {
return nil, err
}
return resp.RawSigs, nil
}
// ComputeInputScript generates the proper input script for P2WPKH output and
// NP2WPKH outputs. This method only requires that the `Output`, `HashType`,
// `SigHashes` and `InputIndex` fields are populated within the sign
// descriptors.
func (s *signerClient) ComputeInputScript(ctx context.Context, tx *wire.MsgTx,
signDescriptors []*input.SignDescriptor) ([]*input.Script, error) {
txRaw, err := swap.EncodeTx(tx)
if err != nil {
return nil, err
}
rpcSignDescs := marshallSignDescriptors(signDescriptors)
rpcCtx, cancel := context.WithTimeout(ctx, rpcTimeout)
defer cancel()
rpcCtx = s.signerMac.WithMacaroonAuth(rpcCtx)
resp, err := s.client.ComputeInputScript(
rpcCtx, &signrpc.SignReq{
RawTxBytes: txRaw,
SignDescs: rpcSignDescs,
},
)
if err != nil {
return nil, err
}
inputScripts := make([]*input.Script, 0, len(resp.InputScripts))
for _, inputScript := range resp.InputScripts {
inputScripts = append(inputScripts, &input.Script{
SigScript: inputScript.SigScript,
Witness: inputScript.Witness,
})
}
return inputScripts, nil
}
// SignMessage signs a message with the key specified in the key locator. The
// returned signature is fixed-size LN wire format encoded.
func (s *signerClient) SignMessage(ctx context.Context, msg []byte,
locator keychain.KeyLocator) ([]byte, error) {
rpcCtx, cancel := context.WithTimeout(ctx, rpcTimeout)
defer cancel()
rpcIn := &signrpc.SignMessageReq{
Msg: msg,
KeyLoc: &signrpc.KeyLocator{
KeyFamily: int32(locator.Family),
KeyIndex: int32(locator.Index),
},
}
rpcCtx = s.signerMac.WithMacaroonAuth(rpcCtx)
resp, err := s.client.SignMessage(rpcCtx, rpcIn)
if err != nil {
return nil, err
}
return resp.Signature, nil
}
// VerifyMessage verifies a signature over a message using the public key
// provided. The signature must be fixed-size LN wire format encoded.
func (s *signerClient) VerifyMessage(ctx context.Context, msg, sig []byte,
pubkey [33]byte) (bool, error) {
rpcCtx, cancel := context.WithTimeout(ctx, rpcTimeout)
defer cancel()
rpcIn := &signrpc.VerifyMessageReq{
Msg: msg,
Signature: sig,
Pubkey: pubkey[:],
}
rpcCtx = s.signerMac.WithMacaroonAuth(rpcCtx)
resp, err := s.client.VerifyMessage(rpcCtx, rpcIn)
if err != nil {
return false, err
}
return resp.Valid, nil
}
// DeriveSharedKey returns a shared secret key by performing Diffie-Hellman key
// derivation between the ephemeral public key and the key specified by the key
// locator (or the node's identity private key if no key locator is specified):
//
// P_shared = privKeyNode * ephemeralPubkey
//
// The resulting shared public key is serialized in the compressed format and
// hashed with SHA256, resulting in a final key length of 256 bits.
func (s *signerClient) DeriveSharedKey(ctx context.Context,
ephemeralPubKey *btcec.PublicKey,
keyLocator *keychain.KeyLocator) ([32]byte, error) {
rpcCtx, cancel := context.WithTimeout(ctx, rpcTimeout)
defer cancel()
rpcIn := &signrpc.SharedKeyRequest{
EphemeralPubkey: ephemeralPubKey.SerializeCompressed(),
KeyLoc: &signrpc.KeyLocator{
KeyFamily: int32(keyLocator.Family),
KeyIndex: int32(keyLocator.Index),
},
}
rpcCtx = s.signerMac.WithMacaroonAuth(rpcCtx)
resp, err := s.client.DeriveSharedKey(rpcCtx, rpcIn)
if err != nil {
return [32]byte{}, err
}
var sharedKey [32]byte
copy(sharedKey[:], resp.SharedKey)
return sharedKey, nil
}

@ -1,68 +0,0 @@
package lndclient
import (
"context"
"fmt"
"strings"
"github.com/lightningnetwork/lnd/lnrpc/verrpc"
"google.golang.org/grpc"
)
// VersionerClient exposes the version of lnd.
type VersionerClient interface {
// GetVersion returns the version and build information of the lnd
// daemon.
GetVersion(ctx context.Context) (*verrpc.Version, error)
}
type versionerClient struct {
client verrpc.VersionerClient
readonlyMac serializedMacaroon
}
func newVersionerClient(conn *grpc.ClientConn,
readonlyMac serializedMacaroon) *versionerClient {
return &versionerClient{
client: verrpc.NewVersionerClient(conn),
readonlyMac: readonlyMac,
}
}
// GetVersion returns the version and build information of the lnd
// daemon.
//
// NOTE: This method is part of the VersionerClient interface.
func (v *versionerClient) GetVersion(ctx context.Context) (*verrpc.Version,
error) {
rpcCtx, cancel := context.WithTimeout(
v.readonlyMac.WithMacaroonAuth(ctx), rpcTimeout,
)
defer cancel()
return v.client.GetVersion(rpcCtx, &verrpc.VersionRequest{})
}
// VersionString returns a nice, human readable string of a version returned by
// the VersionerClient, including all build tags.
func VersionString(version *verrpc.Version) string {
short := VersionStringShort(version)
enabledTags := strings.Join(version.BuildTags, ",")
return fmt.Sprintf("%s, build tags '%s'", short, enabledTags)
}
// VersionStringShort returns a nice, human readable string of a version
// returned by the VersionerClient.
func VersionStringShort(version *verrpc.Version) string {
versionStr := fmt.Sprintf(
"v%d.%d.%d", version.AppMajor, version.AppMinor,
version.AppPatch,
)
if version.AppPreRelease != "" {
versionStr = fmt.Sprintf(
"%s-%s", versionStr, version.AppPreRelease,
)
}
return versionStr
}

@ -1,349 +0,0 @@
package lndclient
import (
"context"
"encoding/hex"
"fmt"
"time"
"github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
"github.com/btcsuite/btcwallet/wtxmgr"
"github.com/lightninglabs/loop/swap"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnrpc/signrpc"
"github.com/lightningnetwork/lnd/lnrpc/walletrpc"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
"google.golang.org/grpc"
)
// WalletKitClient exposes wallet functionality.
type WalletKitClient interface {
// ListUnspent returns a list of all utxos spendable by the wallet with
// a number of confirmations between the specified minimum and maximum.
ListUnspent(ctx context.Context, minConfs, maxConfs int32) (
[]*lnwallet.Utxo, error)
// LeaseOutput locks an output to the given ID, preventing it from being
// available for any future coin selection attempts. The absolute time
// of the lock's expiration is returned. The expiration of the lock can
// be extended by successive invocations of this call. Outputs can be
// unlocked before their expiration through `ReleaseOutput`.
LeaseOutput(ctx context.Context, lockID wtxmgr.LockID,
op wire.OutPoint) (time.Time, error)
// ReleaseOutput unlocks an output, allowing it to be available for coin
// selection if it remains unspent. The ID should match the one used to
// originally lock the output.
ReleaseOutput(ctx context.Context, lockID wtxmgr.LockID,
op wire.OutPoint) error
DeriveNextKey(ctx context.Context, family int32) (
*keychain.KeyDescriptor, error)
DeriveKey(ctx context.Context, locator *keychain.KeyLocator) (
*keychain.KeyDescriptor, error)
NextAddr(ctx context.Context) (btcutil.Address, error)
PublishTransaction(ctx context.Context, tx *wire.MsgTx) error
SendOutputs(ctx context.Context, outputs []*wire.TxOut,
feeRate chainfee.SatPerKWeight) (*wire.MsgTx, error)
EstimateFee(ctx context.Context, confTarget int32) (chainfee.SatPerKWeight,
error)
// ListSweeps returns a list of sweep transaction ids known to our node.
// Note that this function only looks up transaction ids, and does not
// query our wallet for the full set of transactions.
ListSweeps(ctx context.Context) ([]string, error)
}
type walletKitClient struct {
client walletrpc.WalletKitClient
walletKitMac serializedMacaroon
}
// A compile-time constraint to ensure walletKitclient satisfies the
// WalletKitClient interface.
var _ WalletKitClient = (*walletKitClient)(nil)
func newWalletKitClient(conn *grpc.ClientConn,
walletKitMac serializedMacaroon) *walletKitClient {
return &walletKitClient{
client: walletrpc.NewWalletKitClient(conn),
walletKitMac: walletKitMac,
}
}
// ListUnspent returns a list of all utxos spendable by the wallet with a number
// of confirmations between the specified minimum and maximum.
func (m *walletKitClient) ListUnspent(ctx context.Context, minConfs,
maxConfs int32) ([]*lnwallet.Utxo, error) {
rpcCtx, cancel := context.WithTimeout(ctx, rpcTimeout)
defer cancel()
rpcCtx = m.walletKitMac.WithMacaroonAuth(rpcCtx)
resp, err := m.client.ListUnspent(rpcCtx, &walletrpc.ListUnspentRequest{
MinConfs: minConfs,
MaxConfs: maxConfs,
})
if err != nil {
return nil, err
}
utxos := make([]*lnwallet.Utxo, 0, len(resp.Utxos))
for _, utxo := range resp.Utxos {
var addrType lnwallet.AddressType
switch utxo.AddressType {
case lnrpc.AddressType_WITNESS_PUBKEY_HASH:
addrType = lnwallet.WitnessPubKey
case lnrpc.AddressType_NESTED_PUBKEY_HASH:
addrType = lnwallet.NestedWitnessPubKey
default:
return nil, fmt.Errorf("invalid utxo address type %v",
utxo.AddressType)
}
pkScript, err := hex.DecodeString(utxo.PkScript)
if err != nil {
return nil, err
}
opHash, err := chainhash.NewHash(utxo.Outpoint.TxidBytes)
if err != nil {
return nil, err
}
utxos = append(utxos, &lnwallet.Utxo{
AddressType: addrType,
Value: btcutil.Amount(utxo.AmountSat),
Confirmations: utxo.Confirmations,
PkScript: pkScript,
OutPoint: wire.OutPoint{
Hash: *opHash,
Index: utxo.Outpoint.OutputIndex,
},
})
}
return utxos, nil
}
// LeaseOutput locks an output to the given ID, preventing it from being
// available for any future coin selection attempts. The absolute time of the
// lock's expiration is returned. The expiration of the lock can be extended by
// successive invocations of this call. Outputs can be unlocked before their
// expiration through `ReleaseOutput`.
func (m *walletKitClient) LeaseOutput(ctx context.Context, lockID wtxmgr.LockID,
op wire.OutPoint) (time.Time, error) {
rpcCtx, cancel := context.WithTimeout(ctx, rpcTimeout)
defer cancel()
rpcCtx = m.walletKitMac.WithMacaroonAuth(rpcCtx)
resp, err := m.client.LeaseOutput(rpcCtx, &walletrpc.LeaseOutputRequest{
Id: lockID[:],
Outpoint: &lnrpc.OutPoint{
TxidBytes: op.Hash[:],
OutputIndex: op.Index,
},
})
if err != nil {
return time.Time{}, err
}
return time.Unix(int64(resp.Expiration), 0), nil
}
// ReleaseOutput unlocks an output, allowing it to be available for coin
// selection if it remains unspent. The ID should match the one used to
// originally lock the output.
func (m *walletKitClient) ReleaseOutput(ctx context.Context,
lockID wtxmgr.LockID, op wire.OutPoint) error {
rpcCtx, cancel := context.WithTimeout(ctx, rpcTimeout)
defer cancel()
rpcCtx = m.walletKitMac.WithMacaroonAuth(rpcCtx)
_, err := m.client.ReleaseOutput(rpcCtx, &walletrpc.ReleaseOutputRequest{
Id: lockID[:],
Outpoint: &lnrpc.OutPoint{
TxidBytes: op.Hash[:],
OutputIndex: op.Index,
},
})
return err
}
func (m *walletKitClient) DeriveNextKey(ctx context.Context, family int32) (
*keychain.KeyDescriptor, error) {
rpcCtx, cancel := context.WithTimeout(ctx, rpcTimeout)
defer cancel()
rpcCtx = m.walletKitMac.WithMacaroonAuth(rpcCtx)
resp, err := m.client.DeriveNextKey(rpcCtx, &walletrpc.KeyReq{
KeyFamily: family,
})
if err != nil {
return nil, err
}
key, err := btcec.ParsePubKey(resp.RawKeyBytes, btcec.S256())
if err != nil {
return nil, err
}
return &keychain.KeyDescriptor{
KeyLocator: keychain.KeyLocator{
Family: keychain.KeyFamily(resp.KeyLoc.KeyFamily),
Index: uint32(resp.KeyLoc.KeyIndex),
},
PubKey: key,
}, nil
}
func (m *walletKitClient) DeriveKey(ctx context.Context, in *keychain.KeyLocator) (
*keychain.KeyDescriptor, error) {
rpcCtx, cancel := context.WithTimeout(ctx, rpcTimeout)
defer cancel()
rpcCtx = m.walletKitMac.WithMacaroonAuth(rpcCtx)
resp, err := m.client.DeriveKey(rpcCtx, &signrpc.KeyLocator{
KeyFamily: int32(in.Family),
KeyIndex: int32(in.Index),
})
if err != nil {
return nil, err
}
key, err := btcec.ParsePubKey(resp.RawKeyBytes, btcec.S256())
if err != nil {
return nil, err
}
return &keychain.KeyDescriptor{
KeyLocator: *in,
PubKey: key,
}, nil
}
func (m *walletKitClient) NextAddr(ctx context.Context) (
btcutil.Address, error) {
rpcCtx, cancel := context.WithTimeout(ctx, rpcTimeout)
defer cancel()
rpcCtx = m.walletKitMac.WithMacaroonAuth(rpcCtx)
resp, err := m.client.NextAddr(rpcCtx, &walletrpc.AddrRequest{})
if err != nil {
return nil, err
}
addr, err := btcutil.DecodeAddress(resp.Addr, nil)
if err != nil {
return nil, err
}
return addr, nil
}
func (m *walletKitClient) PublishTransaction(ctx context.Context,
tx *wire.MsgTx) error {
txHex, err := swap.EncodeTx(tx)
if err != nil {
return err
}
rpcCtx, cancel := context.WithTimeout(ctx, rpcTimeout)
defer cancel()
rpcCtx = m.walletKitMac.WithMacaroonAuth(rpcCtx)
_, err = m.client.PublishTransaction(rpcCtx, &walletrpc.Transaction{
TxHex: txHex,
})
return err
}
func (m *walletKitClient) SendOutputs(ctx context.Context,
outputs []*wire.TxOut, feeRate chainfee.SatPerKWeight) (
*wire.MsgTx, error) {
rpcOutputs := make([]*signrpc.TxOut, len(outputs))
for i, output := range outputs {
rpcOutputs[i] = &signrpc.TxOut{
PkScript: output.PkScript,
Value: output.Value,
}
}
rpcCtx, cancel := context.WithTimeout(ctx, rpcTimeout)
defer cancel()
rpcCtx = m.walletKitMac.WithMacaroonAuth(rpcCtx)
resp, err := m.client.SendOutputs(rpcCtx, &walletrpc.SendOutputsRequest{
Outputs: rpcOutputs,
SatPerKw: int64(feeRate),
})
if err != nil {
return nil, err
}
tx, err := swap.DecodeTx(resp.RawTx)
if err != nil {
return nil, err
}
return tx, nil
}
func (m *walletKitClient) EstimateFee(ctx context.Context, confTarget int32) (
chainfee.SatPerKWeight, error) {
rpcCtx, cancel := context.WithTimeout(ctx, rpcTimeout)
defer cancel()
rpcCtx = m.walletKitMac.WithMacaroonAuth(rpcCtx)
resp, err := m.client.EstimateFee(rpcCtx, &walletrpc.EstimateFeeRequest{
ConfTarget: int32(confTarget),
})
if err != nil {
return 0, err
}
return chainfee.SatPerKWeight(resp.SatPerKw), nil
}
// ListSweeps returns a list of sweep transaction ids known to our node.
// Note that this function only looks up transaction ids (Verbose=false), and
// does not query our wallet for the full set of transactions.
func (m *walletKitClient) ListSweeps(ctx context.Context) ([]string, error) {
rpcCtx, cancel := context.WithTimeout(ctx, rpcTimeout)
defer cancel()
resp, err := m.client.ListSweeps(
m.walletKitMac.WithMacaroonAuth(rpcCtx),
&walletrpc.ListSweepsRequest{
Verbose: false,
},
)
if err != nil {
return nil, err
}
// Since we have requested the abbreviated response from lnd, we can
// just get our response to a list of sweeps and return it.
sweeps := resp.GetTransactionIds()
return sweeps.TransactionIds, nil
}

@ -10,8 +10,8 @@ import (
"sync/atomic"
proxy "github.com/grpc-ecosystem/grpc-gateway/runtime"
"github.com/lightninglabs/lndclient"
"github.com/lightninglabs/loop"
"github.com/lightninglabs/loop/lndclient"
"github.com/lightninglabs/loop/looprpc"
"github.com/lightningnetwork/lnd/lntypes"
"google.golang.org/grpc"
@ -36,7 +36,8 @@ type listenerCfg struct {
restListener func() (net.Listener, error)
// getLnd returns a grpc connection to an lnd instance.
getLnd func(string, *lndConfig) (*lndclient.GrpcLndServices, error)
getLnd func(lndclient.Network, *lndConfig) (*lndclient.GrpcLndServices,
error)
}
// Daemon is the struct that holds one instance of the loop client daemon.
@ -106,8 +107,10 @@ func (d *Daemon) Start() error {
return errOnlyStartOnce
}
network := lndclient.Network(d.cfg.Network)
var err error
d.lnd, err = d.listenerCfg.getLnd(d.cfg.Network, d.cfg.Lnd)
d.lnd, err = d.listenerCfg.getLnd(network, d.cfg.Lnd)
if err != nil {
return err
}

@ -2,8 +2,8 @@ package loopd
import (
"github.com/btcsuite/btclog"
"github.com/lightninglabs/lndclient"
"github.com/lightninglabs/loop"
"github.com/lightninglabs/loop/lndclient"
"github.com/lightninglabs/loop/loopdb"
"github.com/lightninglabs/loop/lsat"
"github.com/lightningnetwork/lnd/build"

@ -9,8 +9,8 @@ import (
"strings"
"github.com/jessevdk/go-flags"
"github.com/lightninglabs/lndclient"
"github.com/lightninglabs/loop"
"github.com/lightninglabs/loop/lndclient"
"github.com/lightningnetwork/lnd/build"
"github.com/lightningnetwork/lnd/lnrpc/verrpc"
"github.com/lightningnetwork/lnd/signal"
@ -67,7 +67,7 @@ func newListenerCfg(config *Config, rpcCfg RPCConfig) *listenerCfg {
return net.Listen("tcp", config.RESTListen)
},
getLnd: func(network string, cfg *lndConfig) (
getLnd: func(network lndclient.Network, cfg *lndConfig) (
*lndclient.GrpcLndServices, error) {
svcCfg := &lndclient.LndServicesConfig{

@ -12,8 +12,8 @@ import (
"github.com/lightningnetwork/lnd/queue"
"github.com/lightningnetwork/lnd/routing/route"
"github.com/lightninglabs/lndclient"
"github.com/lightninglabs/loop"
"github.com/lightninglabs/loop/lndclient"
"github.com/lightninglabs/loop/loopdb"
"github.com/lightninglabs/loop/swap"

@ -5,8 +5,8 @@ import (
"path/filepath"
"github.com/btcsuite/btcutil"
"github.com/lightninglabs/lndclient"
"github.com/lightninglabs/loop"
"github.com/lightninglabs/loop/lndclient"
)
// getClient returns an instance of the swap client.

@ -4,6 +4,7 @@ import (
"fmt"
"github.com/btcsuite/btcd/chaincfg"
"github.com/lightninglabs/lndclient"
"github.com/lightninglabs/loop"
"github.com/lightninglabs/loop/loopdb"
"github.com/lightninglabs/loop/swap"
@ -11,12 +12,9 @@ import (
// view prints all swaps currently in the database.
func view(config *Config, lisCfg *listenerCfg) error {
chainParams, err := swap.ChainParamsFromNetwork(config.Network)
if err != nil {
return err
}
network := lndclient.Network(config.Network)
lnd, err := lisCfg.getLnd(config.Network, config.Lnd)
lnd, err := lisCfg.getLnd(network, config.Lnd)
if err != nil {
return err
}
@ -28,6 +26,11 @@ func view(config *Config, lisCfg *listenerCfg) error {
}
defer cleanup()
chainParams, err := network.ChainParams()
if err != nil {
return err
}
if err := viewOut(swapClient, chainParams); err != nil {
return err
}

@ -16,7 +16,7 @@ import (
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/lnrpc/invoicesrpc"
"github.com/lightninglabs/loop/lndclient"
"github.com/lightninglabs/lndclient"
"github.com/lightninglabs/loop/loopdb"
"github.com/lightninglabs/loop/swap"
"github.com/lightningnetwork/lnd/lntypes"

@ -4,7 +4,7 @@ import (
"context"
"testing"
"github.com/lightninglabs/loop/lndclient"
"github.com/lightninglabs/lndclient"
"github.com/lightninglabs/loop/loopdb"
"github.com/lightninglabs/loop/swap"
"github.com/lightninglabs/loop/test"

@ -10,7 +10,7 @@ import (
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
"github.com/lightninglabs/loop/lndclient"
"github.com/lightninglabs/lndclient"
"github.com/lightninglabs/loop/loopdb"
"github.com/lightninglabs/loop/swap"
"github.com/lightninglabs/loop/sweep"

@ -10,7 +10,7 @@ import (
"github.com/btcsuite/btcd/blockchain"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
"github.com/lightninglabs/loop/lndclient"
"github.com/lightninglabs/lndclient"
"github.com/lightninglabs/loop/loopdb"
"github.com/lightninglabs/loop/sweep"
"github.com/lightninglabs/loop/test"

@ -9,7 +9,7 @@ import (
"time"
"github.com/btcsuite/btcutil"
"github.com/lightninglabs/loop/lndclient"
"github.com/lightninglabs/lndclient"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/macaroons"

@ -9,7 +9,7 @@ import (
"testing"
"time"
"github.com/lightninglabs/loop/lndclient"
"github.com/lightninglabs/lndclient"
"github.com/lightninglabs/loop/test"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lntypes"

@ -4,7 +4,7 @@ import (
"context"
"time"
"github.com/lightninglabs/loop/lndclient"
"github.com/lightninglabs/lndclient"
"github.com/lightninglabs/loop/loopdb"
"github.com/lightninglabs/loop/swap"
"github.com/lightningnetwork/lnd/lntypes"

@ -1,27 +0,0 @@
package swap
import (
"errors"
"github.com/btcsuite/btcd/chaincfg"
)
// ChainParamsFromNetwork returns chain parameters based on a network name.
func ChainParamsFromNetwork(network string) (*chaincfg.Params, error) {
switch network {
case "mainnet":
return &chaincfg.MainNetParams, nil
case "testnet":
return &chaincfg.TestNet3Params, nil
case "regtest":
return &chaincfg.RegressionNetParams, nil
case "simnet":
return &chaincfg.SimNetParams, nil
default:
return nil, errors.New("unknown network")
}
}

@ -9,30 +9,6 @@ import (
"github.com/btcsuite/btcutil"
)
// EncodeTx encodes a tx to raw bytes.
func EncodeTx(tx *wire.MsgTx) ([]byte, error) {
var buffer bytes.Buffer
err := tx.BtcEncode(&buffer, 0, wire.WitnessEncoding)
if err != nil {
return nil, err
}
rawTx := buffer.Bytes()
return rawTx, nil
}
// DecodeTx decodes raw tx bytes.
func DecodeTx(rawTx []byte) (*wire.MsgTx, error) {
tx := wire.MsgTx{}
r := bytes.NewReader(rawTx)
err := tx.BtcDecode(r, 0, wire.WitnessEncoding)
if err != nil {
return nil, err
}
return &tx, nil
}
// GetScriptOutput locates the given script in the outputs of a transaction and
// returns its outpoint and value.
func GetScriptOutput(htlcTx *wire.MsgTx, scriptHash []byte) (

@ -8,7 +8,7 @@ import (
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
"github.com/lightninglabs/loop/lndclient"
"github.com/lightninglabs/lndclient"
"github.com/lightninglabs/loop/swap"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/keychain"

@ -7,7 +7,7 @@ import (
"time"
"github.com/btcsuite/btcd/wire"
"github.com/lightninglabs/loop/lndclient"
"github.com/lightninglabs/lndclient"
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lntypes"

@ -7,7 +7,7 @@ import (
"time"
"github.com/btcsuite/btcd/btcec"
"github.com/lightninglabs/loop/lndclient"
"github.com/lightninglabs/lndclient"
"github.com/lightningnetwork/lnd/lnrpc/invoicesrpc"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/zpay32"

@ -10,7 +10,7 @@ import (
"github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
"github.com/lightninglabs/loop/lndclient"
"github.com/lightninglabs/lndclient"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/lnrpc/invoicesrpc"
"github.com/lightningnetwork/lnd/lntypes"

@ -8,7 +8,7 @@ import (
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/wire"
"github.com/lightninglabs/loop/lndclient"
"github.com/lightninglabs/lndclient"
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/lntypes"

@ -1,7 +1,7 @@
package test
import (
"github.com/lightninglabs/loop/lndclient"
"github.com/lightninglabs/lndclient"
"github.com/lightningnetwork/lnd/lntypes"
"golang.org/x/net/context"
)

@ -3,7 +3,7 @@ package test
import (
"context"
"github.com/lightninglabs/loop/lndclient"
"github.com/lightninglabs/lndclient"
"github.com/lightningnetwork/lnd/lnrpc/verrpc"
)

@ -10,7 +10,7 @@ import (
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
"github.com/btcsuite/btcwallet/wtxmgr"
"github.com/lightninglabs/loop/lndclient"
"github.com/lightninglabs/lndclient"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"

@ -7,7 +7,7 @@ import (
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
"github.com/lightninglabs/loop/lndclient"
"github.com/lightninglabs/lndclient"
"github.com/lightninglabs/loop/loopdb"
"github.com/lightninglabs/loop/swap"
"github.com/lightninglabs/loop/sweep"

Loading…
Cancel
Save