Merge pull request #39 from guggero/invoice-status

auth: check LSAT invoice status by payment hash
This commit is contained in:
Olaoluwa Osuntokun
2020-08-11 10:38:27 -07:00
committed by GitHub
25 changed files with 2220 additions and 101 deletions

View File

@@ -8,6 +8,7 @@ import (
"net/http"
"os"
"path/filepath"
"sync"
"time"
"github.com/coreos/etcd/clientv3"
@@ -17,6 +18,7 @@ import (
"github.com/lightningnetwork/lnd/build"
"github.com/lightningnetwork/lnd/cert"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/signal"
"github.com/lightningnetwork/lnd/tor"
"golang.org/x/crypto/acme/autocert"
"golang.org/x/net/http2"
@@ -63,16 +65,16 @@ var (
// Main is the true entrypoint of Kirin.
func Main() {
// TODO: Prevent from running twice.
err := start()
err := run()
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
// start sets up the proxy server and runs it. This function blocks until a
// run sets up the proxy server and runs it. This function blocks until a
// shutdown signal is received.
func start() error {
func run() error {
// First, parse configuration file and set up logging.
configFile := filepath.Join(apertureDataDir, defaultConfigFilename)
cfg, err := getConfig(configFile)
@@ -95,14 +97,26 @@ func start() error {
return fmt.Errorf("unable to connect to etcd: %v", err)
}
// Create the proxy and connect it to lnd.
// Create our challenger that uses our backing lnd node to create
// invoices and check their settlement status.
genInvoiceReq := func(price int64) (*lnrpc.Invoice, error) {
return &lnrpc.Invoice{
Memo: "LSAT",
Value: price,
}, nil
}
servicesProxy, err := createProxy(cfg, genInvoiceReq, etcdClient)
challenger, err := NewLndChallenger(cfg.Authenticator, genInvoiceReq)
if err != nil {
return err
}
err = challenger.Start()
if err != nil {
return err
}
defer challenger.Stop()
// Create the proxy and connect it to lnd.
servicesProxy, err := createProxy(cfg, challenger, etcdClient)
if err != nil {
return err
}
@@ -138,17 +152,22 @@ func start() error {
}
}
// The ListenAndServeTLS below will block until shut down or an error
// occurs. So we can just defer a cleanup function here that will close
// everything on shutdown.
defer cleanup(etcdClient, httpsServer)
// Finally start the server.
// Finally run the server.
var (
wg sync.WaitGroup
quit = make(chan struct{})
)
log.Infof("Starting the server, listening on %s.", cfg.ListenAddr)
errChan := make(chan error)
wg.Add(1)
go func() {
errChan <- serveFn()
defer wg.Done()
select {
case errChan <- serveFn():
case <-quit:
}
}()
// If we need to listen over Tor as well, we'll set up the onion
@@ -158,6 +177,7 @@ func start() error {
// will only be reached through the onion services, which already
// provide encryption, so running this additional HTTP server should be
// relatively safe.
var torHTTPServer *http.Server
if cfg.Tor != nil && (cfg.Tor.V2 || cfg.Tor.V3) {
torController, err := initTorListener(cfg, etcdClient)
if err != nil {
@@ -167,17 +187,51 @@ func start() error {
_ = torController.Stop()
}()
httpServer := &http.Server{
torHTTPServer = &http.Server{
Addr: fmt.Sprintf("localhost:%d", cfg.Tor.ListenPort),
Handler: h2c.NewHandler(handler, &http2.Server{}),
}
wg.Add(1)
go func() {
errChan <- httpServer.ListenAndServe()
defer wg.Done()
select {
case errChan <- torHTTPServer.ListenAndServe():
case <-quit:
}
}()
defer httpServer.Close()
}
return <-errChan
// Now that we've started everything, intercept any interrupt signals
// and wait for any of them to arrive.
signal.Intercept()
var returnErr error
select {
case <-signal.ShutdownChannel():
log.Infof("Received interrupt signal, shutting down aperture.")
case err := <-errChan:
log.Errorf("Error while running aperture: %v", err)
returnErr = err
}
// Shut down our client and server connections now. This should cause
// the first goroutine to quit.
cleanup(etcdClient, httpsServer)
// If we started a tor server as well, shut it down now too to cause the
// second goroutine to quit.
if torHTTPServer != nil {
_ = torHTTPServer.Close()
}
// Now we wait for the goroutines to exit before we return. The defers
// will take care of the rest of our started resources.
close(quit)
wg.Wait()
return returnErr
}
// fileExists reports whether the named file or directory exists.
@@ -378,19 +432,15 @@ func initTorListener(cfg *config, etcd *clientv3.Client) (*tor.Controller, error
}
// createProxy creates the proxy with all the services it needs.
func createProxy(cfg *config, genInvoiceReq InvoiceRequestGenerator,
func createProxy(cfg *config, challenger *LndChallenger,
etcdClient *clientv3.Client) (*proxy.Proxy, error) {
challenger, err := NewLndChallenger(cfg.Authenticator, genInvoiceReq)
if err != nil {
return nil, err
}
minter := mint.New(&mint.Config{
Challenger: challenger,
Secrets: newSecretStore(etcdClient),
ServiceLimiter: newStaticServiceLimiter(cfg.Services),
})
authenticator := auth.NewLsatAuthenticator(minter)
authenticator := auth.NewLsatAuthenticator(minter, challenger)
return proxy.New(
authenticator, cfg.Services, cfg.ServeStatic, cfg.StaticRoot,
)

View File

@@ -8,12 +8,14 @@ import (
"github.com/lightninglabs/aperture/lsat"
"github.com/lightninglabs/aperture/mint"
"github.com/lightningnetwork/lnd/lnrpc"
)
// LsatAuthenticator is an authenticator that uses the LSAT protocol to
// authenticate requests.
type LsatAuthenticator struct {
minter Minter
minter Minter
checker InvoiceChecker
}
// A compile time flag to ensure the LsatAuthenticator satisfies the
@@ -22,8 +24,13 @@ var _ Authenticator = (*LsatAuthenticator)(nil)
// NewLsatAuthenticator creates a new authenticator that authenticates requests
// based on LSAT tokens.
func NewLsatAuthenticator(minter Minter) *LsatAuthenticator {
return &LsatAuthenticator{minter: minter}
func NewLsatAuthenticator(minter Minter,
checker InvoiceChecker) *LsatAuthenticator {
return &LsatAuthenticator{
minter: minter,
checker: checker,
}
}
// Accept returns whether or not the header successfully authenticates the user
@@ -51,6 +58,16 @@ func (l *LsatAuthenticator) Accept(header *http.Header, serviceName string) bool
return false
}
// Make sure the backend has the invoice recorded as settled.
err = l.checker.VerifyInvoiceStatus(
preimage.Hash(), lnrpc.Invoice_SETTLED,
DefaultInvoiceLookupTimeout,
)
if err != nil {
log.Debugf("Deny: Invoice status mismatch: %v", err)
return false
}
return true
}

View File

@@ -3,6 +3,7 @@ package auth_test
import (
"encoding/base64"
"encoding/hex"
"fmt"
"net/http"
"testing"
@@ -44,9 +45,10 @@ func TestLsatAuthenticator(t *testing.T) {
testMacBytes,
)
headerTests = []struct {
id string
header *http.Header
result bool
id string
header *http.Header
checkErr error
result bool
}{
{
id: "empty header",
@@ -124,11 +126,23 @@ func TestLsatAuthenticator(t *testing.T) {
},
result: true,
},
{
id: "valid macaroon header, wrong invoice state",
header: &http.Header{
lsat.HeaderMacaroon: []string{
testMacHex,
},
},
checkErr: fmt.Errorf("nope"),
result: false,
},
}
)
a := auth.NewLsatAuthenticator(&mockMint{})
c := &mockChecker{}
a := auth.NewLsatAuthenticator(&mockMint{}, c)
for _, testCase := range headerTests {
c.err = testCase.checkErr
result := a.Accept(testCase.header, "test")
if result != testCase.result {
t.Fatalf("test case %s failed. got %v expected %v",

View File

@@ -3,12 +3,21 @@ package auth
import (
"context"
"net/http"
"time"
"github.com/lightninglabs/aperture/lsat"
"github.com/lightninglabs/aperture/mint"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lntypes"
"gopkg.in/macaroon.v2"
)
const (
// DefaultInvoiceLookupTimeout is the default maximum time we wait for
// an invoice update to arrive.
DefaultInvoiceLookupTimeout = 3 * time.Second
)
// Authenticator is the generic interface for validating client headers and
// returning new challenge headers.
type Authenticator interface {
@@ -30,3 +39,14 @@ type Minter interface {
// VerifyLSAT attempts to verify an LSAT with the given parameters.
VerifyLSAT(context.Context, *mint.VerificationParams) error
}
// InvoiceChecker is an entity that is able to check the status of an invoice,
// particularly whether it's been paid or not.
type InvoiceChecker interface {
// VerifyInvoiceStatus checks that an invoice identified by a payment
// hash has the desired status. To make sure we don't fail while the
// invoice update is still on its way, we try several times until either
// the desired status is set or the given timeout is reached.
VerifyInvoiceStatus(lntypes.Hash, lnrpc.Invoice_InvoiceState,
time.Duration) error
}

View File

@@ -2,10 +2,13 @@ package auth_test
import (
"context"
"time"
"github.com/lightninglabs/aperture/auth"
"github.com/lightninglabs/aperture/lsat"
"github.com/lightninglabs/aperture/mint"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lntypes"
"gopkg.in/macaroon.v2"
)
@@ -23,3 +26,15 @@ func (m *mockMint) MintLSAT(_ context.Context,
func (m *mockMint) VerifyLSAT(_ context.Context, p *mint.VerificationParams) error {
return nil
}
type mockChecker struct {
err error
}
var _ auth.InvoiceChecker = (*mockChecker)(nil)
func (m *mockChecker) VerifyInvoiceStatus(lntypes.Hash,
lnrpc.Invoice_InvoiceState, time.Duration) error {
return m.err
}

View File

@@ -3,30 +3,63 @@ package aperture
import (
"context"
"fmt"
"io"
"math"
"strings"
"sync"
"time"
"github.com/lightninglabs/aperture/auth"
"github.com/lightninglabs/aperture/mint"
"github.com/lightninglabs/loop/lndclient"
"github.com/lightninglabs/lndclient"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lntypes"
"google.golang.org/grpc"
)
// InvoiceRequestGenerator is a function type that returns a new request for the
// lnrpc.AddInvoice call.
type InvoiceRequestGenerator func(price int64) (*lnrpc.Invoice, error)
// InvoiceClient is an interface that only implements part of a full lnd client,
// namely the part around the invoices we need for the challenger to work.
type InvoiceClient interface {
// ListInvoices returns a paginated list of all invoices known to lnd.
ListInvoices(ctx context.Context, in *lnrpc.ListInvoiceRequest,
opts ...grpc.CallOption) (*lnrpc.ListInvoiceResponse, error)
// SubscribeInvoices subscribes to updates on invoices.
SubscribeInvoices(ctx context.Context, in *lnrpc.InvoiceSubscription,
opts ...grpc.CallOption) (
lnrpc.Lightning_SubscribeInvoicesClient, error)
// AddInvoice adds a new invoice to lnd.
AddInvoice(ctx context.Context, in *lnrpc.Invoice,
opts ...grpc.CallOption) (*lnrpc.AddInvoiceResponse, error)
}
// LndChallenger is a challenger that uses an lnd backend to create new LSAT
// payment challenges.
type LndChallenger struct {
client lnrpc.LightningClient
client InvoiceClient
genInvoiceReq InvoiceRequestGenerator
invoiceStates map[lntypes.Hash]lnrpc.Invoice_InvoiceState
invoicesMtx *sync.Mutex
invoicesCancel func()
invoicesCond *sync.Cond
quit chan struct{}
wg sync.WaitGroup
}
// A compile time flag to ensure the LndChallenger satisfies the
// mint.Challenger interface.
// mint.Challenger and auth.InvoiceChecker interface.
var _ mint.Challenger = (*LndChallenger)(nil)
var _ auth.InvoiceChecker = (*LndChallenger)(nil)
const (
// invoiceMacaroonName is the name of the read-only macaroon belonging
// invoiceMacaroonName is the name of the invoice macaroon belonging
// to the target lnd node.
invoiceMacaroonName = "invoice.macaroon"
)
@@ -47,16 +80,162 @@ func NewLndChallenger(cfg *authConfig, genInvoiceReq InvoiceRequestGenerator) (
if err != nil {
return nil, err
}
invoicesMtx := &sync.Mutex{}
return &LndChallenger{
client: client,
genInvoiceReq: genInvoiceReq,
invoiceStates: make(map[lntypes.Hash]lnrpc.Invoice_InvoiceState),
invoicesMtx: invoicesMtx,
invoicesCond: sync.NewCond(invoicesMtx),
quit: make(chan struct{}),
}, nil
}
// Start starts the challenger's main work which is to keep track of all
// invoices and their states. For that the backing lnd node is queried for all
// invoices on startup and the a subscription to all subsequent invoice updates
// is created.
func (l *LndChallenger) Start() error {
// These are the default values for the subscription. In case there are
// no invoices yet, this will instruct lnd to just send us all updates.
// If there are existing invoices, these indices will be updated to
// reflect the latest known invoices.
addIndex := uint64(0)
settleIndex := uint64(0)
// Get a list of all existing invoices on startup and add them to our
// cache. We need to keep track of all invoices, even quite old ones to
// make sure tokens are valid. But to save space we only keep track of
// an invoice's state.
invoiceResp, err := l.client.ListInvoices(
context.Background(), &lnrpc.ListInvoiceRequest{
NumMaxInvoices: math.MaxUint64,
},
)
if err != nil {
return err
}
// Advance our indices to the latest known one so we'll only receive
// updates for new invoices and/or newly settled invoices.
l.invoicesMtx.Lock()
for _, invoice := range invoiceResp.Invoices {
if invoice.AddIndex > addIndex {
addIndex = invoice.AddIndex
}
if invoice.SettleIndex > settleIndex {
settleIndex = invoice.SettleIndex
}
hash, err := lntypes.MakeHash(invoice.RHash)
if err != nil {
l.invoicesMtx.Unlock()
return fmt.Errorf("error parsing invoice hash: %v", err)
}
// Don't track the state of canceled or expired invoices.
if invoiceIrrelevant(invoice) {
continue
}
l.invoiceStates[hash] = invoice.State
}
l.invoicesMtx.Unlock()
// We need to be able to cancel any subscription we make.
ctxc, cancel := context.WithCancel(context.Background())
l.invoicesCancel = cancel
subscriptionResp, err := l.client.SubscribeInvoices(
ctxc, &lnrpc.InvoiceSubscription{
AddIndex: addIndex,
SettleIndex: settleIndex,
},
)
if err != nil {
cancel()
return err
}
l.wg.Add(1)
go func() {
defer l.wg.Done()
defer cancel()
l.readInvoiceStream(subscriptionResp)
}()
return nil
}
// readInvoiceStream reads the invoice update messages sent on the stream until
// the stream is aborted or the challenger is shutting down.
func (l *LndChallenger) readInvoiceStream(
stream lnrpc.Lightning_SubscribeInvoicesClient) {
for {
// In case we receive the shutdown signal right after receiving
// an update, we can exit early.
select {
case <-l.quit:
return
default:
}
// Wait for an update to arrive. This will block until either a
// message receives, an error occurs or the underlying context
// is canceled (which will also result in an error).
invoice, err := stream.Recv()
switch {
case err == io.EOF:
return
case err != nil && strings.Contains(
err.Error(), context.Canceled.Error(),
):
return
case err != nil:
log.Errorf("Received error from invoice subscription: "+
"%v", err)
return
default:
}
hash, err := lntypes.MakeHash(invoice.RHash)
if err != nil {
log.Errorf("Error parsing invoice hash: %v", err)
return
}
l.invoicesMtx.Lock()
if invoiceIrrelevant(invoice) {
// Don't keep the state of canceled or expired invoices.
delete(l.invoiceStates, hash)
} else {
l.invoiceStates[hash] = invoice.State
}
// Before releasing the lock, notify our conditions that listen
// for updates on the invoice state.
l.invoicesCond.Broadcast()
l.invoicesMtx.Unlock()
}
}
// Stop shuts down the challenger.
func (l *LndChallenger) Stop() {
l.invoicesCancel()
close(l.quit)
l.wg.Wait()
}
// NewChallenge creates a new LSAT payment challenge, returning a payment
// request (invoice) and the corresponding payment hash.
//
// NOTE: This is part of the Challenger interface.
// NOTE: This is part of the mint.Challenger interface.
func (l *LndChallenger) NewChallenge(price int64) (string, lntypes.Hash, error) {
// Obtain a new invoice from lnd first. We need to know the payment hash
// so we can add it as a caveat to the macaroon.
@@ -79,3 +258,104 @@ func (l *LndChallenger) NewChallenge(price int64) (string, lntypes.Hash, error)
return response.PaymentRequest, paymentHash, nil
}
// VerifyInvoiceStatus checks that an invoice identified by a payment
// hash has the desired status. To make sure we don't fail while the
// invoice update is still on its way, we try several times until either
// the desired status is set or the given timeout is reached.
//
// NOTE: This is part of the auth.InvoiceChecker interface.
func (l *LndChallenger) VerifyInvoiceStatus(hash lntypes.Hash,
state lnrpc.Invoice_InvoiceState, timeout time.Duration) error {
// Prevent the challenger to be shut down while we're still waiting for
// status updates.
l.wg.Add(1)
defer l.wg.Done()
var (
condWg sync.WaitGroup
doneChan = make(chan struct{})
timeoutReached bool
hasInvoice bool
invoiceState lnrpc.Invoice_InvoiceState
)
// First of all, spawn a goroutine that will signal us on timeout.
// Otherwise if a client subscribes to an update on an invoice that
// never arrives, and there is no other activity, it would block
// forever in the condition.
condWg.Add(1)
go func() {
defer condWg.Done()
select {
case <-doneChan:
case <-time.After(timeout):
case <-l.quit:
}
l.invoicesCond.L.Lock()
timeoutReached = true
l.invoicesCond.Broadcast()
l.invoicesCond.L.Unlock()
}()
// Now create the main goroutine that blocks until an update is received
// on the condition.
condWg.Add(1)
go func() {
defer condWg.Done()
l.invoicesCond.L.Lock()
// Block here until our condition is met or the allowed time is
// up. The Wait() will return whenever a signal is broadcast.
invoiceState, hasInvoice = l.invoiceStates[hash]
for !(hasInvoice && invoiceState == state) && !timeoutReached {
l.invoicesCond.Wait()
// The Wait() above has re-acquired the lock so we can
// safely access the states map.
invoiceState, hasInvoice = l.invoiceStates[hash]
}
// We're now done.
l.invoicesCond.L.Unlock()
close(doneChan)
}()
// Wait until we're either done or timed out.
condWg.Wait()
// Interpret the result so we can return a more descriptive error than
// just "failed".
switch {
case !hasInvoice:
return fmt.Errorf("no active or settled invoice found for "+
"hash=%v", hash)
case invoiceState != state:
return fmt.Errorf("invoice status not correct before timeout, "+
"hash=%v, status=%v", hash, invoiceState)
default:
return nil
}
}
// invoiceIrrelevant returns true if an invoice is nil, canceled or non-settled
// and expired.
func invoiceIrrelevant(invoice *lnrpc.Invoice) bool {
if invoice == nil || invoice.State == lnrpc.Invoice_CANCELED {
return true
}
creation := time.Unix(invoice.CreationDate, 0)
expiration := creation.Add(time.Duration(invoice.Expiry) * time.Second)
expired := time.Now().After(expiration)
notSettled := invoice.State == lnrpc.Invoice_OPEN ||
invoice.State == lnrpc.Invoice_ACCEPTED
return expired && notSettled
}

215
challenger_test.go Normal file
View File

@@ -0,0 +1,215 @@
package aperture
import (
"context"
"sync"
"testing"
"time"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/stretchr/testify/require"
"google.golang.org/grpc"
)
var (
defaultTimeout = 20 * time.Millisecond
)
type invoiceStreamMock struct {
lnrpc.Lightning_SubscribeInvoicesClient
updateChan chan *lnrpc.Invoice
quit chan struct{}
}
func (i *invoiceStreamMock) Recv() (*lnrpc.Invoice, error) {
select {
case msg := <-i.updateChan:
return msg, nil
case <-i.quit:
return nil, context.Canceled
}
}
type mockInvoiceClient struct {
invoices []*lnrpc.Invoice
updateChan chan *lnrpc.Invoice
quit chan struct{}
lastAddIndex uint64
}
// ListInvoices returns a paginated list of all invoices known to lnd.
func (m *mockInvoiceClient) ListInvoices(_ context.Context,
_ *lnrpc.ListInvoiceRequest,
_ ...grpc.CallOption) (*lnrpc.ListInvoiceResponse, error) {
return &lnrpc.ListInvoiceResponse{
Invoices: m.invoices,
}, nil
}
// SubscribeInvoices subscribes to updates on invoices.
func (m *mockInvoiceClient) SubscribeInvoices(_ context.Context,
in *lnrpc.InvoiceSubscription, _ ...grpc.CallOption) (
lnrpc.Lightning_SubscribeInvoicesClient, error) {
m.lastAddIndex = in.AddIndex
return &invoiceStreamMock{
updateChan: m.updateChan,
quit: m.quit,
}, nil
}
// AddInvoice adds a new invoice to lnd.
func (m *mockInvoiceClient) AddInvoice(_ context.Context, in *lnrpc.Invoice,
_ ...grpc.CallOption) (*lnrpc.AddInvoiceResponse, error) {
m.invoices = append(m.invoices, in)
return &lnrpc.AddInvoiceResponse{
RHash: in.RHash,
PaymentRequest: in.PaymentRequest,
AddIndex: uint64(len(m.invoices) - 1),
}, nil
}
func (m *mockInvoiceClient) stop() {
close(m.quit)
}
func newChallenger() (*LndChallenger, *mockInvoiceClient) {
mockClient := &mockInvoiceClient{
updateChan: make(chan *lnrpc.Invoice),
quit: make(chan struct{}),
}
genInvoiceReq := func(price int64) (*lnrpc.Invoice, error) {
return newInvoice(lntypes.ZeroHash, 99, lnrpc.Invoice_OPEN),
nil
}
invoicesMtx := &sync.Mutex{}
return &LndChallenger{
client: mockClient,
genInvoiceReq: genInvoiceReq,
invoiceStates: make(map[lntypes.Hash]lnrpc.Invoice_InvoiceState),
quit: make(chan struct{}),
invoicesMtx: invoicesMtx,
invoicesCond: sync.NewCond(invoicesMtx),
}, mockClient
}
func newInvoice(hash lntypes.Hash, addIndex uint64,
state lnrpc.Invoice_InvoiceState) *lnrpc.Invoice {
return &lnrpc.Invoice{
PaymentRequest: "foo",
RHash: hash[:],
AddIndex: addIndex,
State: state,
CreationDate: time.Now().Unix(),
Expiry: 10,
}
}
func TestLndChallenger(t *testing.T) {
t.Parallel()
// First of all, test that the NewLndChallenger doesn't allow a nil
// invoice generator function.
_, err := NewLndChallenger(nil, nil)
require.Error(t, err)
// Now mock the lnd backend and create a challenger instance that we can
// test.
c, invoiceMock := newChallenger()
// Creating a new challenge should add an invoice to the lnd backend.
req, hash, err := c.NewChallenge(1337)
require.NoError(t, err)
require.Equal(t, "foo", req)
require.Equal(t, lntypes.ZeroHash, hash)
require.Equal(t, 1, len(invoiceMock.invoices))
require.Equal(t, uint64(0), invoiceMock.lastAddIndex)
// Now we already have an invoice in our lnd mock. When starting the
// challenger, we should have that invoice in the cache and a
// subscription that only starts at our faked addIndex.
err = c.Start()
require.NoError(t, err)
require.Equal(t, 1, len(c.invoiceStates))
require.Equal(t, lnrpc.Invoice_OPEN, c.invoiceStates[lntypes.ZeroHash])
require.Equal(t, uint64(99), invoiceMock.lastAddIndex)
require.NoError(t, c.VerifyInvoiceStatus(
lntypes.ZeroHash, lnrpc.Invoice_OPEN, defaultTimeout,
))
require.Error(t, c.VerifyInvoiceStatus(
lntypes.ZeroHash, lnrpc.Invoice_SETTLED, defaultTimeout,
))
// Next, let's send an update for a new invoice and make sure it's added
// to the map.
hash = lntypes.Hash{77, 88, 99}
invoiceMock.updateChan <- newInvoice(hash, 123, lnrpc.Invoice_SETTLED)
require.NoError(t, c.VerifyInvoiceStatus(
hash, lnrpc.Invoice_SETTLED, defaultTimeout,
))
require.Error(t, c.VerifyInvoiceStatus(
hash, lnrpc.Invoice_OPEN, defaultTimeout,
))
// Finally, create a bunch of invoices but only settle the first 5 of
// them. All others should get a failed invoice state after the timeout.
var (
numInvoices = 20
errors = make([]error, numInvoices)
wg sync.WaitGroup
)
for i := 0; i < numInvoices; i++ {
hash := lntypes.Hash{77, 88, 99, byte(i)}
invoiceMock.updateChan <- newInvoice(
hash, 1000+uint64(i), lnrpc.Invoice_OPEN,
)
// The verification will block for a certain time. But we want
// all checks to happen automatically to simulate many parallel
// requests. So we spawn a goroutine for each invoice check.
wg.Add(1)
go func(errIdx int, hash lntypes.Hash) {
defer wg.Done()
errors[errIdx] = c.VerifyInvoiceStatus(
hash, lnrpc.Invoice_SETTLED, defaultTimeout,
)
}(i, hash)
}
// With all 20 goroutines spinning and waiting for updates, we settle
// the first 5 invoices.
for i := 0; i < 5; i++ {
hash := lntypes.Hash{77, 88, 99, byte(i)}
invoiceMock.updateChan <- newInvoice(
hash, 1000+uint64(i), lnrpc.Invoice_SETTLED,
)
}
// Now wait for all checks to finish, then check that the last 15
// invoices timed out.
wg.Wait()
for i := 0; i < numInvoices; i++ {
if i < 5 {
require.NoError(t, errors[i])
} else {
require.Error(t, errors[i])
require.Contains(
t, errors[i].Error(),
"invoice status not correct before timeout",
)
}
}
invoiceMock.stop()
c.Stop()
}

26
go.mod
View File

@@ -3,29 +3,33 @@ module github.com/lightninglabs/aperture
go 1.13
require (
github.com/btcsuite/btcd v0.20.1-beta.0.20200515232429-9f0179fd2c46
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d
github.com/coreos/etcd v3.3.17+incompatible
github.com/btcsuite/btcutil v1.0.2
github.com/btcsuite/btcwallet/wtxmgr v1.2.0
github.com/coreos/etcd v3.3.22+incompatible
github.com/coreos/go-semver v0.3.0 // indirect
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf // indirect
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f // indirect
github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect
github.com/dustin/go-humanize v1.0.0 // indirect
github.com/fortytw2/leaktest v1.3.0
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect
github.com/golang/protobuf v1.3.2
github.com/google/btree v1.0.0 // indirect
github.com/google/uuid v1.1.1 // indirect
github.com/gorilla/websocket v1.4.1 // indirect
github.com/jonboulle/clockwork v0.1.0 // indirect
github.com/json-iterator/go v1.1.9 // indirect
github.com/lightninglabs/loop v0.3.0-alpha.0.20200103135410-5e00ce62677a
github.com/lightningnetwork/lnd v0.9.0-beta-rc4.0.20200313014957-4cb518c17498
github.com/lightningnetwork/lnd/cert v1.0.0
github.com/gorilla/websocket v1.4.2 // indirect
github.com/jonboulle/clockwork v0.2.0 // indirect
github.com/json-iterator/go v1.1.10 // indirect
github.com/lightninglabs/lndclient v1.0.1-0.20200708223031-76709c25d859
github.com/lightningnetwork/lnd v0.10.3-beta
github.com/lightningnetwork/lnd/cert v1.0.2
github.com/modern-go/reflect2 v1.0.1 // indirect
github.com/soheilhy/cmux v0.1.4 // indirect
github.com/stretchr/testify v1.5.1
github.com/tmc/grpc-websocket-proxy v0.0.0-20200122045848-3419fae592fc // indirect
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect
go.uber.org/zap v1.14.1 // indirect
golang.org/x/crypto v0.0.0-20200109152110-61a87790db17
go.uber.org/zap v1.15.0 // indirect
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37
golang.org/x/net v0.0.0-20191112182307-2180aed22343
google.golang.org/grpc v1.25.1
gopkg.in/macaroon.v2 v2.1.0

102
go.sum
View File

@@ -16,6 +16,7 @@ github.com/aead/siphash v1.0.1 h1:FwHfE/T45KPKYuuSAKyyvE+oPWcaQ+CUmFW0bPlM+kg=
github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
@@ -25,13 +26,19 @@ github.com/btcsuite/btcd v0.0.0-20190824003749-130ea5bddde3 h1:A/EVblehb75cUgXA5
github.com/btcsuite/btcd v0.0.0-20190824003749-130ea5bddde3/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI=
github.com/btcsuite/btcd v0.20.1-beta h1:Ik4hyJqN8Jfyv3S4AGBOmyouMsYE3EdYODkMbQjwPGw=
github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
github.com/btcsuite/btcd v0.20.1-beta.0.20200513120220-b470eee47728/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
github.com/btcsuite/btcd v0.20.1-beta.0.20200515232429-9f0179fd2c46 h1:QyTpiR5nQe94vza2qkvf7Ns8XX2Rjh/vdIhO3RzGj4o=
github.com/btcsuite/btcd v0.20.1-beta.0.20200515232429-9f0179fd2c46/go.mod h1:Yktc19YNjh/Iz2//CX0vfRTS4IJKM/RKO5YZ9Fn+Pgo=
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f h1:bAs4lUbRJpnnkd9VhRV3jjAVU7DJVjMaK+IsvSeZvFo=
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA=
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d h1:yJzD/yFppdVCf6ApMkVy8cUxV0XrxdP9rVf6D87/Mng=
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
github.com/btcsuite/btcwallet v0.11.0/go.mod h1:qtPAohN1ioo0pvJt/j7bZM8ANBWlYWVCVFL0kkijs7s=
github.com/btcsuite/btcwallet v0.11.1-0.20200219004649-ae9416ad7623 h1:ZuJRjucNsTmlrbZncsqzD0z3EaXrOobCx2I4lc12R4g=
github.com/btcsuite/btcwallet v0.11.1-0.20200219004649-ae9416ad7623/go.mod h1:1O1uRHMPXHdwA4/od8nqYqrgclVKp+wtfXUAqHmeRvE=
github.com/btcsuite/btcutil v1.0.2 h1:9iZ1Terx9fMIOtq1VrwdqfsATL9MC2l8ZrUY6YZ2uts=
github.com/btcsuite/btcutil v1.0.2/go.mod h1:j9HUFwoQRsZL3V4n+qG+CUnEGHOarIxfC3Le2Yhbcts=
github.com/btcsuite/btcutil/psbt v1.0.2 h1:gCVY3KxdoEVU7Q6TjusPO+GANIwVgr9yTLqM+a6CZr8=
github.com/btcsuite/btcutil/psbt v1.0.2/go.mod h1:LVveMu4VaNSkIRTZu2+ut0HDBRuYjqGocxDMNS1KuGQ=
github.com/btcsuite/btcwallet v0.11.1-0.20200612012534-48addcd5591a h1:AZ1Mf0gd9mgJqrTTIFUc17ep9EKUbQusVAIzJ6X+x3Q=
github.com/btcsuite/btcwallet v0.11.1-0.20200612012534-48addcd5591a/go.mod h1:9+AH3V5mcTtNXTKe+fe63fDLKGOwQbZqmvOVUef+JFE=
github.com/btcsuite/btcwallet/wallet/txauthor v1.0.0 h1:KGHMW5sd7yDdDMkCZ/JpP0KltolFsQcB973brBnfj4c=
github.com/btcsuite/btcwallet/wallet/txauthor v1.0.0/go.mod h1:VufDts7bd/zs3GV13f/lXc/0lXrPnvxD/NvmpG/FEKU=
github.com/btcsuite/btcwallet/wallet/txrules v1.0.0 h1:2VsfS0sBedcM5KmDzRMT3+b6xobqWveZGvjb+jFez5w=
@@ -40,13 +47,16 @@ github.com/btcsuite/btcwallet/wallet/txsizes v1.0.0 h1:6DxkcoMnCPY4E9cUDPB5tbuuf
github.com/btcsuite/btcwallet/wallet/txsizes v1.0.0/go.mod h1:pauEU8UuMFiThe5PB3EO+gO5kx87Me5NvdQDsTuq6cs=
github.com/btcsuite/btcwallet/walletdb v1.0.0 h1:mheT7vCWK5EP6rZzhxsQ7ms9+yX4VE8bwiJctECBeNw=
github.com/btcsuite/btcwallet/walletdb v1.0.0/go.mod h1:bZTy9RyYZh9fLnSua+/CD48TJtYJSHjjYcSaszuxCCk=
github.com/btcsuite/btcwallet/walletdb v1.1.0/go.mod h1:bZTy9RyYZh9fLnSua+/CD48TJtYJSHjjYcSaszuxCCk=
github.com/btcsuite/btcwallet/walletdb v1.2.0 h1:E0+M4jHOToAvGWZ27ew5AaDAHDi6fUiXkjUJUnoEOD0=
github.com/btcsuite/btcwallet/walletdb v1.2.0/go.mod h1:9cwc1Yyg4uvd4ZdfdoMnALji+V9gfWSMfxEdLdR5Vwc=
github.com/btcsuite/btcwallet/walletdb v1.3.1/go.mod h1:9cwc1Yyg4uvd4ZdfdoMnALji+V9gfWSMfxEdLdR5Vwc=
github.com/btcsuite/btcwallet/walletdb v1.3.2/go.mod h1:GZCMPNpUu5KE3ASoVd+k06p/1OW8OwNGCCaNWRto2cQ=
github.com/btcsuite/btcwallet/walletdb v1.3.3 h1:u6e7vRIKBF++cJy+hOHaMGg+88ZTwvpaY27AFvtB668=
github.com/btcsuite/btcwallet/walletdb v1.3.3/go.mod h1:oJDxAEUHVtnmIIBaa22wSBPTVcs6hUp5NKWmI8xDwwU=
github.com/btcsuite/btcwallet/wtxmgr v1.0.0 h1:aIHgViEmZmZfe0tQQqF1xyd2qBqFWxX5vZXkkbjtbeA=
github.com/btcsuite/btcwallet/wtxmgr v1.0.0/go.mod h1:vc4gBprll6BP0UJ+AIGDaySoc7MdAmZf8kelfNb8CFY=
github.com/btcsuite/fastsha256 v0.0.0-20160815193821-637e65642941 h1:kij1x2aL7VE6gtx8KMIt8PGPgI5GV9LgtHFG5KaEMPY=
github.com/btcsuite/fastsha256 v0.0.0-20160815193821-637e65642941/go.mod h1:QcFA8DZHtuIAdYKCq/BzELOaznRsCvwf4zTPmaYwaig=
github.com/btcsuite/btcwallet/wtxmgr v1.2.0 h1:ZUYPsSv8GjF9KK7lboB2OVHF0uYEcHxgrCfFWqPd9NA=
github.com/btcsuite/btcwallet/wtxmgr v1.2.0/go.mod h1:h8hkcKUE3X7lMPzTUoGnNiw5g7VhGrKEW3KpR2r0VnY=
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd h1:R/opQEbFEy9JGkIguV40SvRY1uliPX8ifOvi6ICsFCw=
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg=
github.com/btcsuite/golangcrypto v0.0.0-20150304025918-53f62d9b43e8/go.mod h1:tYvUd8KLhm/oXvUeSEs2VlLghFjQt9+ZaF9ghH0JNjc=
@@ -64,8 +74,8 @@ github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghf
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/coreos/bbolt v1.3.3 h1:n6AiVyVRKQFNb6mJlwESEvvLoDyiTzXX7ORAUlkeBdY=
github.com/coreos/bbolt v1.3.3/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.17+incompatible h1:f/Z3EoDSx1yjaIjLQGo1diYUlQYSBrrAQ5vP8NjwXwo=
github.com/coreos/etcd v3.3.17+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/etcd v3.3.22+incompatible h1:AnRMUyVdVvh1k7lHe61YEd227+CLoNogQuAypztGSK4=
github.com/coreos/etcd v3.3.22+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf h1:iW4rZ826su+pqaw19uhpSCzhj44qo35pNgKFGqzDKkU=
@@ -79,6 +89,8 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
@@ -94,6 +106,8 @@ github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-openapi/errors v0.19.2/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94=
github.com/go-openapi/strfmt v0.19.5/go.mod h1:eftuHTlB/dI8Uq8JJOyRlieZf+WkkxUuk0dgdHXr2Qk=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gogo/protobuf v1.1.1 h1:72R+M5VuhED/KujmZVcIquuo8mBgX4oVda//DQb3PXo=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
@@ -111,36 +125,36 @@ 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.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg=
github.com/google/go-cmp v0.3.1/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/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=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM=
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 h1:Iju5GlWwrvL6UBg4zJJt3btmonfrMlCDdsejg4CZE7c=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.8.6 h1:XvND7+MPP7Jp+JpqSZ7naSl5nVZf6k0LbL1V3EKh0zc=
github.com/grpc-ecosystem/grpc-gateway v1.8.6/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/grpc-ecosystem/grpc-gateway v1.10.0 h1:yqx/nTDLC6pVrQ8fTaCeeeMJNbmt7HglUpysQATYXV4=
github.com/grpc-ecosystem/grpc-gateway v1.10.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/jackpal/gateway v1.0.5 h1:qzXWUJfuMdlLMtt0a3Dgt+xkWQiA5itDEITVJtuSwMc=
github.com/jackpal/gateway v1.0.5/go.mod h1:lTpwd4ACLXmpyiCTRtfiNyVnUmqT9RivzCDQetPfnjA=
github.com/jackpal/go-nat-pmp v0.0.0-20170405195558-28a68d0c24ad h1:heFfj7z0pGsNCekUlsFhO2jstxO4b5iQ665LjwM5mDc=
github.com/jackpal/go-nat-pmp v0.0.0-20170405195558-28a68d0c24ad/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc=
github.com/jedib0t/go-pretty v4.3.0+incompatible/go.mod h1:XemHduiw8R651AF9Pt4FwCTKeG3oo7hrHJAoznj9nag=
github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jonboulle/clockwork v0.1.0 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0hcPo=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/jonboulle/clockwork v0.2.0 h1:J2SLSdy7HgElq8ekSl2Mxh6vrRNFxqbXGenYH2I02Vs=
github.com/jonboulle/clockwork v0.2.0/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8=
github.com/jrick/logrotate v1.0.0 h1:lQ1bL/n9mBNeIXoTUoYRlK4dHuNJVofX9oWqBtPnSzI=
github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ=
github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/juju/clock v0.0.0-20190205081909-9c5c9712527c h1:3UvYABOQRhJAApj9MdCN+Ydv841ETSoy6xLzdmmr/9A=
github.com/juju/clock v0.0.0-20190205081909-9c5c9712527c/go.mod h1:nD0vlnrUjcjJhqN5WuCWZyzfd5AHZAC9/ajvbSx69xA=
github.com/juju/errors v0.0.0-20190806202954-0232dcc7464d h1:hJXjZMxj0SWlMoQkzeZDLi2cmeiWKa7y1B8Rg+qaoEc=
@@ -170,32 +184,36 @@ 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/loop v0.3.0-alpha.0.20200103135410-5e00ce62677a h1:EiZsid2PZaanlYn5x41hKttdoxnd3rJ34SotnO4bu4Y=
github.com/lightninglabs/loop v0.3.0-alpha.0.20200103135410-5e00ce62677a/go.mod h1:VEp7TFUL7Ehq9mjQVUuNVTjpMIeTx8kOHvQ7PdJpLuA=
github.com/lightninglabs/lndclient v1.0.1-0.20200708223031-76709c25d859 h1:MrKZJ35EuWPAXR1tS8KCGL+hcsGwhC2jtL0AqlXXOvY=
github.com/lightninglabs/lndclient v1.0.1-0.20200708223031-76709c25d859/go.mod h1:nKEd6j54LxPYuIe3K/BUwQPus24sbvWTdoweoBcHas0=
github.com/lightninglabs/neutrino v0.11.0 h1:lPpYFCtsfJX2W5zI4pWycPmbbBdr7zU+BafYdLoD6k0=
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=
github.com/lightninglabs/protobuf-hex-display v1.3.3-0.20191212020323-b444784ce75d/go.mod h1:KDb67YMzoh4eudnzClmvs2FbiLG9vxISmLApUkCa4uI=
github.com/lightningnetwork/lightning-onion v0.0.0-20191214001659-f34e9dc1651d/go.mod h1:rigfi6Af/KqsF7Za0hOgcyq2PNH4AN70AaMRxcJkff4=
github.com/lightningnetwork/lightning-onion v1.0.1 h1:qChGgS5+aPxFeR6JiUsGvanei1bn6WJpYbvosw/1604=
github.com/lightningnetwork/lightning-onion v1.0.1/go.mod h1:rigfi6Af/KqsF7Za0hOgcyq2PNH4AN70AaMRxcJkff4=
github.com/lightningnetwork/lnd v0.8.0-beta-rc3.0.20200103000305-22e1f006b194/go.mod h1:WHK90FD3m2n6OyWzondS7ho0Uhtgfp30Nxvj24lQYX4=
github.com/lightningnetwork/lnd v0.9.0-beta-rc4.0.20200313014957-4cb518c17498 h1:v9WzPrzY/BOPfs7D1Lg5IuIxhao3BAJaL8EFFcLTzTY=
github.com/lightningnetwork/lnd v0.9.0-beta-rc4.0.20200313014957-4cb518c17498/go.mod h1:fImtTwhIXK91glN8iArkrGuScc0sNEKpZq43pTvVq3w=
github.com/lightningnetwork/lnd/cert v1.0.0 h1:J0gtf2UNQX2U+/j5cXnX2wIMSTuJuwrXv7m9qJr2wtw=
github.com/lightningnetwork/lnd/cert v1.0.0/go.mod h1:fmtemlSMf5t4hsQmcprSoOykypAPp+9c+0d0iqTScMo=
github.com/lightningnetwork/lnd v0.10.3-beta h1:bS9+zMEH7DHAc8yzEml/xBMCiB0Xo5K7R84hKLvO4ao=
github.com/lightningnetwork/lnd v0.10.3-beta/go.mod h1:4d02pduRVtZwgTJ+EimKJTsEAY0jDwi0SPE9h5aRneM=
github.com/lightningnetwork/lnd/cert v1.0.2 h1:g2rEu+sM2Uyz0bpfuvwri/ks6R/26H5iY1NcGbpDJ+c=
github.com/lightningnetwork/lnd/cert v1.0.2/go.mod h1:fmtemlSMf5t4hsQmcprSoOykypAPp+9c+0d0iqTScMo=
github.com/lightningnetwork/lnd/clock v1.0.1 h1:QQod8+m3KgqHdvVMV+2DRNNZS1GRFir8mHZYA+Z2hFo=
github.com/lightningnetwork/lnd/clock v1.0.1/go.mod h1:KnQudQ6w0IAMZi1SgvecLZQZ43ra2vpDNj7H/aasemg=
github.com/lightningnetwork/lnd/queue v1.0.1 h1:jzJKcTy3Nj5lQrooJ3aaw9Lau3I0IwvQR5sqtjdv2R0=
github.com/lightningnetwork/lnd/queue v1.0.1/go.mod h1:vaQwexir73flPW43Mrm7JOgJHmcEFBWWSl9HlyASoms=
github.com/lightningnetwork/lnd/queue v1.0.2 h1:Hx43fmTz2pDH4fIYDr57P/M5cB+GEMLzN+eif8576Xo=
github.com/lightningnetwork/lnd/queue v1.0.2/go.mod h1:YTkTVZCxz8tAYreH27EO3s8572ODumWrNdYW2E/YKxg=
github.com/lightningnetwork/lnd/queue v1.0.4 h1:8Dq3vxAFSACPy+pKN88oPFhuCpCoAAChPBwa4BJxH4k=
github.com/lightningnetwork/lnd/queue v1.0.4/go.mod h1:YTkTVZCxz8tAYreH27EO3s8572ODumWrNdYW2E/YKxg=
github.com/lightningnetwork/lnd/ticker v1.0.0 h1:S1b60TEGoTtCe2A0yeB+ecoj/kkS4qpwh6l+AkQEZwU=
github.com/lightningnetwork/lnd/ticker v1.0.0/go.mod h1:iaLXJiVgI1sPANIF2qYYUJXjoksPNvGNYowB8aRbpX0=
github.com/ltcsuite/ltcd v0.0.0-20190101042124-f37f8bf35796 h1:sjOGyegMIhvgfq5oaue6Td+hxZuf3tDC8lAPrFldqFw=
github.com/ltcsuite/ltcd v0.0.0-20190101042124-f37f8bf35796/go.mod h1:3p7ZTf9V1sNPI5H8P3NkTFF4LuwMdPl2DodF60qAKqY=
github.com/ltcsuite/ltcutil v0.0.0-20181217130922-17f3b04680b6/go.mod h1:8Vg/LTOO0KYa/vlHWJ6XZAevPQThGH5sufO0Hrou/lA=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/miekg/dns v0.0.0-20171125082028-79bfde677fa8 h1:PRMAcldsl4mXKJeRNB/KVNz6TlbS6hk2Rs42PqgU3Ws=
github.com/miekg/dns v0.0.0-20171125082028-79bfde677fa8/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
@@ -240,38 +258,43 @@ github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4k
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/tmc/grpc-websocket-proxy v0.0.0-20200122045848-3419fae592fc h1:yUaosFVTJwnltaHbSNC3i82I92quFs+OFPRl8kNMVwo=
github.com/tmc/grpc-websocket-proxy v0.0.0-20200122045848-3419fae592fc/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/tv42/zbase32 v0.0.0-20160707012821-501572607d02 h1:tcJ6OjwOMvExLlzrAVZute09ocAGa7KqOON60++Gz4E=
github.com/tv42/zbase32 v0.0.0-20160707012821-501572607d02/go.mod h1:tHlrkM198S068ZqfrO6S8HsoJq2bF3ETfTL+kt4tInY=
github.com/urfave/cli v1.18.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
go.etcd.io/bbolt v1.3.3 h1:MUGmc65QhB3pIlaQ5bB4LwqSj6GIonVJXpZiaKNyaKk=
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/bbolt v1.3.5-0.20200615073812-232d8fc87f50 h1:ASw9n1EHMftwnP3Az4XW6e308+gNsrHzmdhd0Olz9Hs=
go.etcd.io/bbolt v1.3.5-0.20200615073812-232d8fc87f50/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk=
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A=
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4=
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
go.uber.org/zap v1.14.1 h1:nYDKopTbvAPq/NrUVZwT15y2lpROBiLLyoRTbXOYWOo=
go.uber.org/zap v1.14.1/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc=
go.uber.org/zap v1.15.0 h1:ZZCA22JRF2gQE5FoNmhmrf7jeJJ2uhqDUNRYKm8dvmM=
go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc=
golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20180723164146-c126467f60eb/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190829043050-9756ffdc2472 h1:Gv7RPwsi3eZ2Fgewe3CBsuOebPwO27PoXzRpJPsvSSM=
golang.org/x/crypto v0.0.0-20190829043050-9756ffdc2472/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200109152110-61a87790db17 h1:nVJ3guKA9qdkEQ3TUdXI9QSINo2CUPM/cySEvw2w8I0=
golang.org/x/crypto v0.0.0-20200109152110-61a87790db17/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37 h1:cg5LA/zNPRzIXIWSCxQW10Rvpy94aQh3LT/ShoCpkHw=
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@@ -292,8 +315,6 @@ golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73r
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 h1:k7pJ2yAPLPgbskkFdhRCsA77k2fySZ1zf2zCjvQCiIM=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191112182307-2180aed22343 h1:00ohfJ4K98s3m6BGUoBd8nyfp4Yl0GoIKvw5abItTjI=
golang.org/x/net v0.0.0-20191112182307-2180aed22343/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -312,14 +333,14 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd h1:DBH9mDw0zluJT/R+nGuV3jWFWLFaHyYZWD4tOT+cjn0=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5 h1:LfCXLvNmTYH9kEmVgqbnsWfruoXZIrh4YBgqVHtDvw0=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/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=
@@ -340,6 +361,7 @@ google.golang.org/grpc v1.18.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0 h1:AzbTB6ux+okLTzP8Ru1Xs41C303zdcfEht7MQnYJt5A=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA=
google.golang.org/grpc v1.25.1 h1:wdKvqQk7IttEw92GoRyKG2IDrUIpgpj6H6m81yfeMW0=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=

View File

@@ -0,0 +1,160 @@
package test
import (
"bytes"
"sync"
"time"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/chainntnfs"
"golang.org/x/net/context"
)
type mockChainNotifier struct {
sync.Mutex
lnd *LndMockServices
confRegistrations []*ConfRegistration
wg sync.WaitGroup
}
// SpendRegistration contains registration details.
type SpendRegistration struct {
Outpoint *wire.OutPoint
PkScript []byte
HeightHint int32
}
// ConfRegistration contains registration details.
type ConfRegistration struct {
TxID *chainhash.Hash
PkScript []byte
HeightHint int32
NumConfs int32
ConfChan chan *chainntnfs.TxConfirmation
}
func (c *mockChainNotifier) RegisterSpendNtfn(ctx context.Context,
outpoint *wire.OutPoint, pkScript []byte, heightHint int32) (
chan *chainntnfs.SpendDetail, chan error, error) {
c.lnd.RegisterSpendChannel <- &SpendRegistration{
HeightHint: heightHint,
Outpoint: outpoint,
PkScript: pkScript,
}
spendChan := make(chan *chainntnfs.SpendDetail, 1)
errChan := make(chan error, 1)
c.wg.Add(1)
go func() {
defer c.wg.Done()
select {
case m := <-c.lnd.SpendChannel:
select {
case spendChan <- m:
case <-ctx.Done():
}
case <-ctx.Done():
}
}()
return spendChan, errChan, nil
}
func (c *mockChainNotifier) WaitForFinished() {
c.wg.Wait()
}
func (c *mockChainNotifier) RegisterBlockEpochNtfn(ctx context.Context) (
chan int32, chan error, error) {
blockErrorChan := make(chan error, 1)
blockEpochChan := make(chan int32)
c.wg.Add(1)
go func() {
defer c.wg.Done()
// Send initial block height
select {
case blockEpochChan <- c.lnd.Height:
case <-ctx.Done():
return
}
for {
select {
case m := <-c.lnd.epochChannel:
select {
case blockEpochChan <- m:
case <-ctx.Done():
return
}
case <-ctx.Done():
return
}
}
}()
return blockEpochChan, blockErrorChan, nil
}
func (c *mockChainNotifier) RegisterConfirmationsNtfn(ctx context.Context,
txid *chainhash.Hash, pkScript []byte, numConfs, heightHint int32) (
chan *chainntnfs.TxConfirmation, chan error, error) {
reg := &ConfRegistration{
PkScript: pkScript,
TxID: txid,
HeightHint: heightHint,
NumConfs: numConfs,
ConfChan: make(chan *chainntnfs.TxConfirmation, 1),
}
c.Lock()
c.confRegistrations = append(c.confRegistrations, reg)
c.Unlock()
errChan := make(chan error, 1)
c.wg.Add(1)
go func() {
defer c.wg.Done()
select {
case m := <-c.lnd.ConfChannel:
c.Lock()
for i := 0; i < len(c.confRegistrations); i++ {
r := c.confRegistrations[i]
// Whichever conf notifier catches the confirmation
// will forward it to all matching subscibers.
if bytes.Equal(m.Tx.TxOut[0].PkScript, r.PkScript) {
// Unregister the "notifier".
c.confRegistrations = append(
c.confRegistrations[:i], c.confRegistrations[i+1:]...,
)
i--
select {
case r.ConfChan <- m:
case <-ctx.Done():
}
}
}
c.Unlock()
case <-ctx.Done():
}
}()
select {
case c.lnd.RegisterConfChannel <- reg:
case <-time.After(Timeout):
return nil, nil, ErrTimeout
}
return reg.ConfChan, errChan, nil
}

256
internal/test/context.go Normal file
View File

@@ -0,0 +1,256 @@
package test
import (
"bytes"
"crypto/sha256"
"testing"
"time"
"github.com/btcsuite/btcd/wire"
"github.com/lightninglabs/lndclient"
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/zpay32"
)
// Context contains shared test context functions.
type Context struct {
T *testing.T
Lnd *LndMockServices
FailedInvoices map[lntypes.Hash]struct{}
PaidInvoices map[string]func(error)
}
// NewContext instanties a new common test context.
func NewContext(t *testing.T,
lnd *LndMockServices) Context {
return Context{
T: t,
Lnd: lnd,
FailedInvoices: make(map[lntypes.Hash]struct{}),
PaidInvoices: make(map[string]func(error)),
}
}
// ReceiveTx receives and decodes a published tx.
func (ctx *Context) ReceiveTx() *wire.MsgTx {
ctx.T.Helper()
select {
case tx := <-ctx.Lnd.TxPublishChannel:
return tx
case <-time.After(Timeout):
ctx.T.Fatalf("sweep not published")
return nil
}
}
// NotifySpend simulates a spend.
func (ctx *Context) NotifySpend(tx *wire.MsgTx, inputIndex uint32) {
ctx.T.Helper()
txHash := tx.TxHash()
select {
case ctx.Lnd.SpendChannel <- &chainntnfs.SpendDetail{
SpendingTx: tx,
SpenderTxHash: &txHash,
SpenderInputIndex: inputIndex,
}:
case <-time.After(Timeout):
ctx.T.Fatalf("htlc spend not consumed")
}
}
// NotifyConf simulates a conf.
func (ctx *Context) NotifyConf(tx *wire.MsgTx) {
ctx.T.Helper()
select {
case ctx.Lnd.ConfChannel <- &chainntnfs.TxConfirmation{
Tx: tx,
}:
case <-time.After(Timeout):
ctx.T.Fatalf("htlc spend not consumed")
}
}
// AssertRegisterSpendNtfn asserts that a register for spend has been received.
func (ctx *Context) AssertRegisterSpendNtfn(script []byte) {
ctx.T.Helper()
select {
case spendIntent := <-ctx.Lnd.RegisterSpendChannel:
if !bytes.Equal(spendIntent.PkScript, script) {
ctx.T.Fatalf("server not listening for published htlc script")
}
case <-time.After(Timeout):
DumpGoroutines()
ctx.T.Fatalf("spend not subscribed to")
}
}
// AssertTrackPayment asserts that a call was made to track payment, and
// returns the track payment message so that it can be used to send updates
// to the test.
func (ctx *Context) AssertTrackPayment() TrackPaymentMessage {
ctx.T.Helper()
var msg TrackPaymentMessage
select {
case msg = <-ctx.Lnd.TrackPaymentChannel:
case <-time.After(Timeout):
DumpGoroutines()
ctx.T.Fatalf("payment not tracked")
}
return msg
}
// AssertRegisterConf asserts that a register for conf has been received.
func (ctx *Context) AssertRegisterConf(expectTxHash bool) *ConfRegistration {
ctx.T.Helper()
// Expect client to register for conf
var confIntent *ConfRegistration
select {
case confIntent = <-ctx.Lnd.RegisterConfChannel:
switch {
case expectTxHash && confIntent.TxID == nil:
ctx.T.Fatalf("expected tx id for registration")
case !expectTxHash && confIntent.TxID != nil:
ctx.T.Fatalf("expected script only registration")
}
case <-time.After(Timeout):
ctx.T.Fatalf("htlc confirmed not subscribed to")
}
return confIntent
}
// AssertPaid asserts that the expected payment request has been paid. This
// function returns a complete function to signal the final payment result.
func (ctx *Context) AssertPaid(
expectedMemo string) func(error) {
ctx.T.Helper()
if done, ok := ctx.PaidInvoices[expectedMemo]; ok {
return done
}
// Assert that client pays swap invoice.
for {
var swapPayment RouterPaymentChannelMessage
select {
case swapPayment = <-ctx.Lnd.RouterSendPaymentChannel:
case <-time.After(Timeout):
ctx.T.Fatalf("no payment sent for invoice: %v",
expectedMemo)
}
payReq := ctx.DecodeInvoice(swapPayment.Invoice)
if _, ok := ctx.PaidInvoices[*payReq.Description]; ok {
ctx.T.Fatalf("duplicate invoice paid: %v",
*payReq.Description)
}
done := func(result error) {
if result != nil {
swapPayment.Errors <- result
return
}
swapPayment.Updates <- lndclient.PaymentStatus{
State: lnrpc.Payment_SUCCEEDED,
}
}
ctx.PaidInvoices[*payReq.Description] = done
if *payReq.Description == expectedMemo {
return done
}
}
}
// AssertSettled asserts that an invoice with the given hash is settled.
func (ctx *Context) AssertSettled(
expectedHash lntypes.Hash) lntypes.Preimage {
ctx.T.Helper()
select {
case preimage := <-ctx.Lnd.SettleInvoiceChannel:
hash := sha256.Sum256(preimage[:])
if expectedHash != hash {
ctx.T.Fatalf("server claims with wrong preimage")
}
return preimage
case <-time.After(Timeout):
}
ctx.T.Fatalf("invoice not settled")
return lntypes.Preimage{}
}
// AssertFailed asserts that an invoice with the given hash is failed.
func (ctx *Context) AssertFailed(expectedHash lntypes.Hash) {
ctx.T.Helper()
if _, ok := ctx.FailedInvoices[expectedHash]; ok {
return
}
for {
select {
case hash := <-ctx.Lnd.FailInvoiceChannel:
ctx.FailedInvoices[expectedHash] = struct{}{}
if expectedHash == hash {
return
}
case <-time.After(Timeout):
ctx.T.Fatalf("invoice not failed")
}
}
}
// DecodeInvoice decodes a payment request string.
func (ctx *Context) DecodeInvoice(request string) *zpay32.Invoice {
ctx.T.Helper()
payReq, err := ctx.Lnd.DecodeInvoice(request)
if err != nil {
ctx.T.Fatal(err)
}
return payReq
}
// GetOutputIndex returns the index in the tx outs of the given script hash.
func (ctx *Context) GetOutputIndex(tx *wire.MsgTx,
script []byte) int {
for idx, out := range tx.TxOut {
if bytes.Equal(out.PkScript, script) {
return idx
}
}
ctx.T.Fatal("htlc not present in tx")
return 0
}
// NotifyServerHeight notifies the server of the arrival of a new block and
// waits for the notification to be processed by selecting on a
// dedicated test channel.
func (ctx *Context) NotifyServerHeight(height int32) {
if err := ctx.Lnd.NotifyHeight(height); err != nil {
ctx.T.Fatal(err)
}
}

View File

@@ -0,0 +1,110 @@
package test
import (
"context"
"fmt"
"sync"
"time"
"github.com/btcsuite/btcd/btcec"
"github.com/lightninglabs/lndclient"
"github.com/lightningnetwork/lnd/lnrpc/invoicesrpc"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/zpay32"
)
type mockInvoices struct {
lnd *LndMockServices
wg sync.WaitGroup
}
func (s *mockInvoices) SettleInvoice(ctx context.Context,
preimage lntypes.Preimage) error {
logger.Infof("Settle invoice %v with preimage %v", preimage.Hash(),
preimage)
s.lnd.SettleInvoiceChannel <- preimage
return nil
}
func (s *mockInvoices) WaitForFinished() {
s.wg.Wait()
}
func (s *mockInvoices) CancelInvoice(ctx context.Context,
hash lntypes.Hash) error {
s.lnd.FailInvoiceChannel <- hash
return nil
}
func (s *mockInvoices) SubscribeSingleInvoice(ctx context.Context,
hash lntypes.Hash) (<-chan lndclient.InvoiceUpdate,
<-chan error, error) {
updateChan := make(chan lndclient.InvoiceUpdate, 2)
errChan := make(chan error)
select {
case s.lnd.SingleInvoiceSubcribeChannel <- &SingleInvoiceSubscription{
Update: updateChan,
Err: errChan,
Hash: hash,
}:
case <-ctx.Done():
return nil, nil, ctx.Err()
}
return updateChan, errChan, nil
}
func (s *mockInvoices) AddHoldInvoice(ctx context.Context,
in *invoicesrpc.AddInvoiceData) (string, error) {
s.lnd.lock.Lock()
defer s.lnd.lock.Unlock()
hash := in.Hash
// Create and encode the payment request as a bech32 (zpay32) string.
creationDate := time.Now()
payReq, err := zpay32.NewInvoice(
s.lnd.ChainParams, *hash, creationDate,
zpay32.Description(in.Memo),
zpay32.CLTVExpiry(in.CltvExpiry),
zpay32.Amount(in.Value),
)
if err != nil {
return "", err
}
privKey, err := btcec.NewPrivateKey(btcec.S256())
if err != nil {
return "", err
}
payReqString, err := payReq.Encode(
zpay32.MessageSigner{
SignCompact: func(hash []byte) ([]byte, error) {
// btcec.SignCompact returns a pubkey-recoverable signature
sig, err := btcec.SignCompact(
btcec.S256(), privKey, hash, true,
)
if err != nil {
return nil, fmt.Errorf("can't sign the hash: %v", err)
}
return sig, nil
},
},
)
if err != nil {
return "", err
}
return payReqString, nil
}

17
internal/test/keys.go Normal file
View File

@@ -0,0 +1,17 @@
package test
import (
"github.com/btcsuite/btcd/btcec"
)
// CreateKey returns a deterministically generated key pair.
func CreateKey(index int32) (*btcec.PrivateKey, *btcec.PublicKey) {
// Avoid all zeros, because it results in an invalid key.
privKey, pubKey := btcec.PrivKeyFromBytes(btcec.S256(),
[]byte{0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, byte(index + 1)})
return privKey, pubKey
}

View File

@@ -0,0 +1,240 @@
package test
import (
"crypto/rand"
"encoding/hex"
"fmt"
"sync"
"time"
"github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
"github.com/lightninglabs/lndclient"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/lnrpc/invoicesrpc"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/zpay32"
"golang.org/x/net/context"
)
type mockLightningClient struct {
lnd *LndMockServices
wg sync.WaitGroup
}
// PayInvoice pays an invoice.
func (h *mockLightningClient) PayInvoice(ctx context.Context, invoice string,
maxFee btcutil.Amount,
outgoingChannel *uint64) chan lndclient.PaymentResult {
done := make(chan lndclient.PaymentResult, 1)
h.lnd.SendPaymentChannel <- PaymentChannelMessage{
PaymentRequest: invoice,
Done: done,
}
return done
}
func (h *mockLightningClient) WaitForFinished() {
h.wg.Wait()
}
func (h *mockLightningClient) ConfirmedWalletBalance(ctx context.Context) (
btcutil.Amount, error) {
return 1000000, nil
}
func (h *mockLightningClient) GetInfo(ctx context.Context) (*lndclient.Info,
error) {
pubKeyBytes, err := hex.DecodeString(h.lnd.NodePubkey)
if err != nil {
return nil, err
}
var pubKey [33]byte
copy(pubKey[:], pubKeyBytes)
return &lndclient.Info{
BlockHeight: 600,
IdentityPubkey: pubKey,
Uris: []string{h.lnd.NodePubkey + "@127.0.0.1:9735"},
}, nil
}
func (h *mockLightningClient) EstimateFeeToP2WSH(ctx context.Context,
amt btcutil.Amount, confTarget int32) (btcutil.Amount,
error) {
return 3000, nil
}
func (h *mockLightningClient) AddInvoice(ctx context.Context,
in *invoicesrpc.AddInvoiceData) (lntypes.Hash, string, error) {
h.lnd.lock.Lock()
defer h.lnd.lock.Unlock()
var hash lntypes.Hash
switch {
case in.Hash != nil:
hash = *in.Hash
case in.Preimage != nil:
hash = (*in.Preimage).Hash()
default:
if _, err := rand.Read(hash[:]); err != nil {
return lntypes.Hash{}, "", err
}
}
// Create and encode the payment request as a bech32 (zpay32) string.
creationDate := time.Now()
payReq, err := zpay32.NewInvoice(
h.lnd.ChainParams, hash, creationDate,
zpay32.Description(in.Memo),
zpay32.CLTVExpiry(in.CltvExpiry),
zpay32.Amount(in.Value),
)
if err != nil {
return lntypes.Hash{}, "", err
}
privKey, err := btcec.NewPrivateKey(btcec.S256())
if err != nil {
return lntypes.Hash{}, "", err
}
payReqString, err := payReq.Encode(
zpay32.MessageSigner{
SignCompact: func(hash []byte) ([]byte, error) {
// btcec.SignCompact returns a pubkey-recoverable signature
sig, err := btcec.SignCompact(
btcec.S256(), privKey, hash, true,
)
if err != nil {
return nil, fmt.Errorf("can't sign the hash: %v", err)
}
return sig, nil
},
},
)
if err != nil {
return lntypes.Hash{}, "", err
}
// Add the invoice we have created to our mock's set of invoices.
h.lnd.Invoices[hash] = &lndclient.Invoice{
Preimage: nil,
Hash: hash,
PaymentRequest: payReqString,
Amount: in.Value,
CreationDate: creationDate,
State: channeldb.ContractOpen,
IsKeysend: false,
}
return hash, payReqString, nil
}
// LookupInvoice looks up an invoice in the mock's set of stored invoices.
// If it is not found, this call will fail. Note that these invoices should
// be settled using settleInvoice to have a preimage, settled state and settled
// date set.
func (h *mockLightningClient) LookupInvoice(_ context.Context,
hash lntypes.Hash) (*lndclient.Invoice, error) {
h.lnd.lock.Lock()
defer h.lnd.lock.Unlock()
inv, ok := h.lnd.Invoices[hash]
if !ok {
return nil, fmt.Errorf("invoice: %x not found", hash)
}
return inv, nil
}
// ListTransactions returns all known transactions of the backing lnd node.
func (h *mockLightningClient) ListTransactions(
_ context.Context) ([]lndclient.Transaction, error) {
h.lnd.lock.Lock()
txs := h.lnd.Transactions
h.lnd.lock.Unlock()
return txs, nil
}
// ListChannels retrieves all channels of the backing lnd node.
func (h *mockLightningClient) ListChannels(ctx context.Context) (
[]lndclient.ChannelInfo, error) {
return h.lnd.Channels, nil
}
// ClosedChannels returns a list of our closed channels.
func (h *mockLightningClient) ClosedChannels(_ context.Context) ([]lndclient.ClosedChannel,
error) {
return h.lnd.ClosedChannels, nil
}
// ForwardingHistory returns the mock's set of forwarding events.
func (h *mockLightningClient) ForwardingHistory(_ context.Context,
_ lndclient.ForwardingHistoryRequest) (*lndclient.ForwardingHistoryResponse,
error) {
return &lndclient.ForwardingHistoryResponse{
LastIndexOffset: 0,
Events: h.lnd.ForwardingEvents,
}, nil
}
// ListInvoices returns our mock's invoices.
func (h *mockLightningClient) ListInvoices(_ context.Context,
_ lndclient.ListInvoicesRequest) (*lndclient.ListInvoicesResponse,
error) {
invoices := make([]lndclient.Invoice, 0, len(h.lnd.Invoices))
for _, invoice := range h.lnd.Invoices {
invoices = append(invoices, *invoice)
}
return &lndclient.ListInvoicesResponse{
Invoices: invoices,
}, nil
}
// ListPayments makes a paginated call to our list payments endpoint.
func (h *mockLightningClient) ListPayments(_ context.Context,
_ lndclient.ListPaymentsRequest) (*lndclient.ListPaymentsResponse,
error) {
return &lndclient.ListPaymentsResponse{
Payments: h.lnd.Payments,
}, nil
}
// ChannelBackup retrieves the backup for a particular channel. The
// backup is returned as an encrypted chanbackup.Single payload.
func (h *mockLightningClient) ChannelBackup(context.Context, wire.OutPoint) ([]byte, error) {
return nil, nil
}
// ChannelBackups retrieves backups for all existing pending open and
// open channels. The backups are returned as an encrypted
// chanbackup.Multi payload.
func (h *mockLightningClient) ChannelBackups(ctx context.Context) ([]byte, error) {
return nil, nil
}
// DecodePaymentRequest decodes a payment request.
func (h *mockLightningClient) DecodePaymentRequest(_ context.Context,
_ string) (*lndclient.PaymentRequest, error) {
return nil, nil
}

View File

@@ -0,0 +1,263 @@
package test
import (
"context"
"errors"
"sync"
"time"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/wire"
"github.com/lightninglabs/lndclient"
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
"github.com/lightningnetwork/lnd/zpay32"
)
var (
testStartingHeight = int32(600)
testNodePubkey = "03f5374b16f0b1f1b49101de1b9d89e0b460bc57ce9c2f9" +
"132b73dfc76d3704daa"
testSignature = []byte{55, 66, 77, 88, 99}
testSignatureMsg = "test"
)
// NewMockLnd returns a new instance of LndMockServices that can be used in unit
// tests.
func NewMockLnd() *LndMockServices {
lightningClient := &mockLightningClient{}
walletKit := &mockWalletKit{
feeEstimates: make(map[int32]chainfee.SatPerKWeight),
}
chainNotifier := &mockChainNotifier{}
signer := &mockSigner{}
invoices := &mockInvoices{}
router := &mockRouter{}
versioner := newMockVersioner()
lnd := LndMockServices{
LndServices: lndclient.LndServices{
WalletKit: walletKit,
Client: lightningClient,
ChainNotifier: chainNotifier,
Signer: signer,
Invoices: invoices,
Router: router,
ChainParams: &chaincfg.TestNet3Params,
Versioner: versioner,
},
SendPaymentChannel: make(chan PaymentChannelMessage),
ConfChannel: make(chan *chainntnfs.TxConfirmation),
RegisterConfChannel: make(chan *ConfRegistration),
RegisterSpendChannel: make(chan *SpendRegistration),
SpendChannel: make(chan *chainntnfs.SpendDetail),
TxPublishChannel: make(chan *wire.MsgTx),
SendOutputsChannel: make(chan wire.MsgTx),
SettleInvoiceChannel: make(chan lntypes.Preimage),
SingleInvoiceSubcribeChannel: make(chan *SingleInvoiceSubscription),
RouterSendPaymentChannel: make(chan RouterPaymentChannelMessage),
TrackPaymentChannel: make(chan TrackPaymentMessage),
SignOutputRawChannel: make(chan SignOutputRawRequest),
FailInvoiceChannel: make(chan lntypes.Hash, 2),
epochChannel: make(chan int32),
Height: testStartingHeight,
NodePubkey: testNodePubkey,
Signature: testSignature,
SignatureMsg: testSignatureMsg,
Invoices: make(map[lntypes.Hash]*lndclient.Invoice),
}
lightningClient.lnd = &lnd
chainNotifier.lnd = &lnd
walletKit.lnd = &lnd
invoices.lnd = &lnd
router.lnd = &lnd
signer.lnd = &lnd
// Also simulate the cached info that is loaded on startup.
info, _ := lightningClient.GetInfo(context.Background())
version, _ := versioner.GetVersion(context.Background())
lnd.LndServices.NodeAlias = info.Alias
lnd.LndServices.NodePubkey = info.IdentityPubkey
lnd.LndServices.Version = version
lnd.WaitForFinished = func() {
chainNotifier.WaitForFinished()
lightningClient.WaitForFinished()
invoices.WaitForFinished()
}
return &lnd
}
// PaymentChannelMessage is the data that passed through SendPaymentChannel.
type PaymentChannelMessage struct {
PaymentRequest string
Done chan lndclient.PaymentResult
}
// TrackPaymentMessage is the data that passed through TrackPaymentChannel.
type TrackPaymentMessage struct {
Hash lntypes.Hash
Updates chan lndclient.PaymentStatus
Errors chan error
}
// RouterPaymentChannelMessage is the data that passed through RouterSendPaymentChannel.
type RouterPaymentChannelMessage struct {
lndclient.SendPaymentRequest
TrackPaymentMessage
}
// SingleInvoiceSubscription contains the single invoice subscribers
type SingleInvoiceSubscription struct {
Hash lntypes.Hash
Update chan lndclient.InvoiceUpdate
Err chan error
}
// SignOutputRawRequest contains input data for a tx signing request.
type SignOutputRawRequest struct {
Tx *wire.MsgTx
SignDescriptors []*input.SignDescriptor
}
// LndMockServices provides a full set of mocked lnd services.
type LndMockServices struct {
lndclient.LndServices
SendPaymentChannel chan PaymentChannelMessage
SpendChannel chan *chainntnfs.SpendDetail
TxPublishChannel chan *wire.MsgTx
SendOutputsChannel chan wire.MsgTx
SettleInvoiceChannel chan lntypes.Preimage
FailInvoiceChannel chan lntypes.Hash
epochChannel chan int32
ConfChannel chan *chainntnfs.TxConfirmation
RegisterConfChannel chan *ConfRegistration
RegisterSpendChannel chan *SpendRegistration
SingleInvoiceSubcribeChannel chan *SingleInvoiceSubscription
RouterSendPaymentChannel chan RouterPaymentChannelMessage
TrackPaymentChannel chan TrackPaymentMessage
SignOutputRawChannel chan SignOutputRawRequest
Height int32
NodePubkey string
Signature []byte
SignatureMsg string
Transactions []lndclient.Transaction
Sweeps []string
// Invoices is a set of invoices that have been created by the mock,
// keyed by hash string.
Invoices map[lntypes.Hash]*lndclient.Invoice
Channels []lndclient.ChannelInfo
ClosedChannels []lndclient.ClosedChannel
ForwardingEvents []lndclient.ForwardingEvent
Payments []lndclient.Payment
WaitForFinished func()
lock sync.Mutex
}
// NotifyHeight notifies a new block height.
func (s *LndMockServices) NotifyHeight(height int32) error {
s.Height = height
select {
case s.epochChannel <- height:
case <-time.After(Timeout):
return ErrTimeout
}
return nil
}
// AddRelevantTx marks the given transaction as relevant.
func (s *LndMockServices) AddTx(tx *wire.MsgTx) {
s.lock.Lock()
s.Transactions = append(s.Transactions, lndclient.Transaction{
Tx: tx.Copy(),
})
s.lock.Unlock()
}
// IsDone checks whether all channels have been fully emptied. If not this may
// indicate unexpected behaviour of the code under test.
func (s *LndMockServices) IsDone() error {
select {
case <-s.SendPaymentChannel:
return errors.New("SendPaymentChannel not empty")
default:
}
select {
case <-s.SpendChannel:
return errors.New("SpendChannel not empty")
default:
}
select {
case <-s.TxPublishChannel:
return errors.New("TxPublishChannel not empty")
default:
}
select {
case <-s.SendOutputsChannel:
return errors.New("SendOutputsChannel not empty")
default:
}
select {
case <-s.SettleInvoiceChannel:
return errors.New("SettleInvoiceChannel not empty")
default:
}
select {
case <-s.ConfChannel:
return errors.New("ConfChannel not empty")
default:
}
select {
case <-s.RegisterConfChannel:
return errors.New("RegisterConfChannel not empty")
default:
}
select {
case <-s.RegisterSpendChannel:
return errors.New("RegisterSpendChannel not empty")
default:
}
return nil
}
// DecodeInvoice decodes a payment request string.
func (s *LndMockServices) DecodeInvoice(request string) (*zpay32.Invoice,
error) {
return zpay32.Decode(request, s.ChainParams)
}
func (s *LndMockServices) SetFeeEstimate(confTarget int32,
feeEstimate chainfee.SatPerKWeight) {
s.WalletKit.(*mockWalletKit).feeEstimates[confTarget] = feeEstimate
}

24
internal/test/log.go Normal file
View File

@@ -0,0 +1,24 @@
package test
import (
"os"
"github.com/btcsuite/btclog"
)
// 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 (
backendLog = btclog.NewBackend(logWriter{})
logger = backendLog.Logger("TEST")
)
// logWriter implements an io.Writer that outputs to both standard output and
// the write-end pipe of an initialized log rotator.
type logWriter struct{}
func (logWriter) Write(p []byte) (n int, err error) {
os.Stdout.Write(p)
return len(p), nil
}

View File

@@ -0,0 +1,43 @@
package test
import (
"github.com/lightninglabs/lndclient"
"github.com/lightningnetwork/lnd/lntypes"
"golang.org/x/net/context"
)
type mockRouter struct {
lnd *LndMockServices
}
func (r *mockRouter) SendPayment(ctx context.Context,
request lndclient.SendPaymentRequest) (chan lndclient.PaymentStatus,
chan error, error) {
statusChan := make(chan lndclient.PaymentStatus)
errorChan := make(chan error)
r.lnd.RouterSendPaymentChannel <- RouterPaymentChannelMessage{
SendPaymentRequest: request,
TrackPaymentMessage: TrackPaymentMessage{
Updates: statusChan,
Errors: errorChan,
},
}
return statusChan, errorChan, nil
}
func (r *mockRouter) TrackPayment(ctx context.Context,
hash lntypes.Hash) (chan lndclient.PaymentStatus, chan error, error) {
statusChan := make(chan lndclient.PaymentStatus)
errorChan := make(chan error)
r.lnd.TrackPaymentChannel <- TrackPaymentMessage{
Hash: hash,
Updates: statusChan,
Errors: errorChan,
}
return statusChan, errorChan, nil
}

View File

@@ -0,0 +1,58 @@
package test
import (
"bytes"
"context"
"fmt"
"github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/keychain"
)
type mockSigner struct {
lnd *LndMockServices
}
func (s *mockSigner) SignOutputRaw(ctx context.Context, tx *wire.MsgTx,
signDescriptors []*input.SignDescriptor) ([][]byte, error) {
s.lnd.SignOutputRawChannel <- SignOutputRawRequest{
Tx: tx,
SignDescriptors: signDescriptors,
}
rawSigs := [][]byte{{1, 2, 3}}
return rawSigs, nil
}
func (s *mockSigner) ComputeInputScript(ctx context.Context, tx *wire.MsgTx,
signDescriptors []*input.SignDescriptor) ([]*input.Script, error) {
return nil, fmt.Errorf("unimplemented")
}
func (s *mockSigner) SignMessage(ctx context.Context, msg []byte,
locator keychain.KeyLocator) ([]byte, error) {
return s.lnd.Signature, nil
}
func (s *mockSigner) VerifyMessage(ctx context.Context, msg, sig []byte,
pubkey [33]byte) (bool, error) {
// Make the mock somewhat functional by asserting that the message and
// signature is what we expect from the mock parameters.
mockAssertion := bytes.Equal(msg, []byte(s.lnd.SignatureMsg)) &&
bytes.Equal(sig, s.lnd.Signature)
return mockAssertion, nil
}
func (s *mockSigner) DeriveSharedKey(context.Context, *btcec.PublicKey,
*keychain.KeyLocator) ([32]byte, error) {
return [32]byte{4, 5, 6}, nil
}

View File

@@ -0,0 +1,98 @@
package test
import (
"errors"
"fmt"
"os"
"runtime/pprof"
"testing"
"time"
"github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcutil"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/zpay32"
)
var (
// Timeout is the default timeout when tests wait for something to
// happen.
Timeout = time.Second * 5
// ErrTimeout is returned on timeout.
ErrTimeout = errors.New("test timeout")
testTime = time.Date(2018, time.January, 9, 14, 00, 00, 0, time.UTC)
)
// GetDestAddr deterministically generates a sweep address for testing.
func GetDestAddr(t *testing.T, nr byte) btcutil.Address {
destAddr, err := btcutil.NewAddressScriptHash([]byte{nr},
&chaincfg.MainNetParams)
if err != nil {
t.Fatal(err)
}
return destAddr
}
// EncodePayReq encodes a zpay32 invoice with a fixed key.
func EncodePayReq(payReq *zpay32.Invoice) (string, error) {
privKey, _ := CreateKey(5)
reqString, err := payReq.Encode(
zpay32.MessageSigner{
SignCompact: func(hash []byte) ([]byte, error) {
// btcec.SignCompact returns a
// pubkey-recoverable signature
sig, err := btcec.SignCompact(
btcec.S256(),
privKey,
payReq.PaymentHash[:],
true,
)
if err != nil {
return nil, fmt.Errorf(
"can't sign the hash: %v", err)
}
return sig, nil
},
},
)
if err != nil {
return "", err
}
return reqString, nil
}
// GetInvoice creates a testnet payment request with the given parameters.
func GetInvoice(hash lntypes.Hash, amt btcutil.Amount, memo string) (
string, error) {
req, err := zpay32.NewInvoice(
&chaincfg.TestNet3Params, hash, testTime,
zpay32.Description(memo),
zpay32.Amount(lnwire.NewMSatFromSatoshis(amt)),
)
if err != nil {
return "", err
}
reqString, err := EncodePayReq(req)
if err != nil {
return "", err
}
return reqString, nil
}
// DumpGoroutines dumps all currently running goroutines.
func DumpGoroutines() {
err := pprof.Lookup("goroutine").WriteTo(os.Stdout, 1)
if err != nil {
panic(err)
}
}

34
internal/test/timeout.go Normal file
View File

@@ -0,0 +1,34 @@
package test
import (
"os"
"runtime/pprof"
"testing"
"time"
"github.com/fortytw2/leaktest"
)
// Guard implements a test level timeout.
func Guard(t *testing.T) func() {
done := make(chan struct{})
go func() {
select {
case <-time.After(5 * time.Second):
err := pprof.Lookup("goroutine").WriteTo(os.Stdout, 1)
if err != nil {
panic(err)
}
panic("test timeout")
case <-done:
}
}()
fn := leaktest.Check(t)
return func() {
close(done)
fn()
}
}

View File

@@ -0,0 +1,51 @@
package test
import (
"context"
"github.com/lightninglabs/lndclient"
"github.com/lightningnetwork/lnd/lnrpc/verrpc"
)
const (
defaultMockCommit = "v0.99.9-beta"
defaultMockCommitHash = "0000000000000000000000000000000000000000"
defaultMockVersion = "v0.99.9-beta"
defaultMockAppMajor = 0
defaultMockAppMinor = 99
defaultMockAppPatch = 9
defaultMockAppPrerelease = "beta"
defaultMockAppGoVersion = "go1.99.9"
)
var (
defaultMockBuildTags = []string{
"signrpc", "walletrpc", "chainrpc", "invoicesrpc",
}
)
type mockVersioner struct {
version *verrpc.Version
}
var _ lndclient.VersionerClient = (*mockVersioner)(nil)
func newMockVersioner() *mockVersioner {
return &mockVersioner{
version: &verrpc.Version{
Commit: defaultMockCommit,
CommitHash: defaultMockCommitHash,
Version: defaultMockVersion,
AppMajor: defaultMockAppMajor,
AppMinor: defaultMockAppMinor,
AppPatch: defaultMockAppPatch,
AppPreRelease: defaultMockAppPrerelease,
BuildTags: defaultMockBuildTags,
GoVersion: defaultMockAppGoVersion,
},
}
}
func (v *mockVersioner) GetVersion(_ context.Context) (*verrpc.Version, error) {
return v.version, nil
}

View File

@@ -0,0 +1,133 @@
package test
import (
"context"
"errors"
"time"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
"github.com/btcsuite/btcwallet/wtxmgr"
"github.com/lightninglabs/lndclient"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
)
type mockWalletKit struct {
lnd *LndMockServices
keyIndex int32
feeEstimates map[int32]chainfee.SatPerKWeight
}
var _ lndclient.WalletKitClient = (*mockWalletKit)(nil)
func (m *mockWalletKit) ListUnspent(ctx context.Context, minConfs,
maxConfs int32) ([]*lnwallet.Utxo, error) {
return nil, nil
}
func (m *mockWalletKit) LeaseOutput(ctx context.Context, lockID wtxmgr.LockID,
op wire.OutPoint) (time.Time, error) {
return time.Now(), nil
}
func (m *mockWalletKit) ReleaseOutput(ctx context.Context,
lockID wtxmgr.LockID, op wire.OutPoint) error {
return nil
}
func (m *mockWalletKit) DeriveNextKey(ctx context.Context, family int32) (
*keychain.KeyDescriptor, error) {
index := m.keyIndex
_, pubKey := CreateKey(index)
m.keyIndex++
return &keychain.KeyDescriptor{
KeyLocator: keychain.KeyLocator{
Family: keychain.KeyFamily(family),
Index: uint32(index),
},
PubKey: pubKey,
}, nil
}
func (m *mockWalletKit) DeriveKey(ctx context.Context, in *keychain.KeyLocator) (
*keychain.KeyDescriptor, error) {
_, pubKey := CreateKey(int32(in.Index))
return &keychain.KeyDescriptor{
KeyLocator: *in,
PubKey: pubKey,
}, nil
}
func (m *mockWalletKit) NextAddr(ctx context.Context) (btcutil.Address, error) {
addr, err := btcutil.NewAddressWitnessPubKeyHash(
make([]byte, 20), &chaincfg.TestNet3Params,
)
if err != nil {
return nil, err
}
return addr, nil
}
func (m *mockWalletKit) PublishTransaction(ctx context.Context, tx *wire.MsgTx) error {
m.lnd.AddTx(tx)
m.lnd.TxPublishChannel <- tx
return nil
}
func (m *mockWalletKit) SendOutputs(ctx context.Context, outputs []*wire.TxOut,
feeRate chainfee.SatPerKWeight) (*wire.MsgTx, error) {
var inputTxHash chainhash.Hash
tx := wire.MsgTx{}
tx.AddTxIn(&wire.TxIn{
PreviousOutPoint: wire.OutPoint{
Hash: inputTxHash,
Index: 0,
},
})
for _, out := range outputs {
tx.AddTxOut(&wire.TxOut{
PkScript: out.PkScript,
Value: out.Value,
})
}
m.lnd.AddTx(&tx)
m.lnd.SendOutputsChannel <- tx
return &tx, nil
}
func (m *mockWalletKit) EstimateFee(ctx context.Context, confTarget int32) (
chainfee.SatPerKWeight, error) {
if confTarget <= 1 {
return 0, errors.New("conf target must be greater than 1")
}
feeEstimate, ok := m.feeEstimates[confTarget]
if !ok {
return 10000, nil
}
return feeEstimate, nil
}
// ListSweeps returns a list of the sweep transaction ids known to our node.
func (m *mockWalletKit) ListSweeps(_ context.Context) ([]string, error) {
return m.lnd.Sweeps, nil
}

View File

@@ -9,8 +9,8 @@ import (
"time"
"github.com/btcsuite/btcutil"
"github.com/lightninglabs/loop/lndclient"
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
"github.com/lightninglabs/lndclient"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/zpay32"
"google.golang.org/grpc"
@@ -400,13 +400,13 @@ func (i *ClientInterceptor) trackPayment(ctx context.Context, token *Token) erro
// If the payment was successful, we have all the
// information we need and we can return the fully paid
// token.
case routerrpc.PaymentState_SUCCEEDED:
case lnrpc.Payment_SUCCEEDED:
extractPaymentDetails(token, result)
return i.store.StoreToken(token)
// The payment is still in transit, we'll give it more
// time to complete.
case routerrpc.PaymentState_IN_FLIGHT:
case lnrpc.Payment_IN_FLIGHT:
// Any other state means either error or timeout.
default:
@@ -441,8 +441,6 @@ func isPaymentRequired(err error) bool {
// from the payment status and stores them in the token.
func extractPaymentDetails(token *Token, status lndclient.PaymentStatus) {
token.Preimage = status.Preimage
total := status.Route.TotalAmount
fees := status.Route.TotalFees()
token.AmountPaid = total - fees
token.RoutingFeePaid = fees
token.AmountPaid = status.Value
token.RoutingFeePaid = status.Fee
}

View File

@@ -9,11 +9,10 @@ import (
"testing"
"time"
"github.com/lightninglabs/loop/lndclient"
"github.com/lightninglabs/loop/test"
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
"github.com/lightninglabs/aperture/internal/test"
"github.com/lightninglabs/lndclient"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/routing/route"
"google.golang.org/grpc"
"google.golang.org/grpc/status"
"gopkg.in/macaroon.v2"
@@ -160,9 +159,8 @@ var (
// return an error.
resetBackend(nil, "")
msg.Updates <- lndclient.PaymentStatus{
State: routerrpc.PaymentState_SUCCEEDED,
State: lnrpc.Payment_SUCCEEDED,
Preimage: paidPreimage,
Route: &route.Route{},
}
},
expectToken: true,

View File

@@ -8,7 +8,6 @@ import (
"net/http"
"regexp"
"github.com/lightninglabs/loop/lsat"
"github.com/lightningnetwork/lnd/lntypes"
"gopkg.in/macaroon.v2"
)
@@ -111,7 +110,7 @@ func FromHeader(header *http.Header) (*macaroon.Macaroon, lntypes.Preimage, erro
return nil, lntypes.Preimage{}, fmt.Errorf("unable to "+
"unmarshal macaroon: %v", err)
}
preimageHex, ok := lsat.HasCaveat(mac, lsat.PreimageKey)
preimageHex, ok := HasCaveat(mac, PreimageKey)
if !ok {
return nil, lntypes.Preimage{}, errors.New("preimage caveat " +
"not found")