mirror of https://github.com/lightninglabs/loop
Merge pull request #237 from joostjager/external-lndclient
lndclient: move to github.com/lightninglabs/lndclientpull/239/head lndclient-split
commit
fc8f3c9986
@ -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
|
||||
}
|
@ -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")
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue