mirror of
https://github.com/lightninglabs/aperture.git
synced 2026-01-27 13:14:34 +01:00
Merge pull request #39 from guggero/invoice-status
auth: check LSAT invoice status by payment hash
This commit is contained in:
94
aperture.go
94
aperture.go
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
290
challenger.go
290
challenger.go
@@ -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
215
challenger_test.go
Normal 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
26
go.mod
@@ -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
102
go.sum
@@ -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=
|
||||
|
||||
160
internal/test/chainnotifier_mock.go
Normal file
160
internal/test/chainnotifier_mock.go
Normal 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
256
internal/test/context.go
Normal 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)
|
||||
}
|
||||
}
|
||||
110
internal/test/invoices_mock.go
Normal file
110
internal/test/invoices_mock.go
Normal 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
17
internal/test/keys.go
Normal 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
|
||||
}
|
||||
240
internal/test/lightning_client_mock.go
Normal file
240
internal/test/lightning_client_mock.go
Normal 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
|
||||
}
|
||||
263
internal/test/lnd_services_mock.go
Normal file
263
internal/test/lnd_services_mock.go
Normal 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
24
internal/test/log.go
Normal 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
|
||||
}
|
||||
43
internal/test/router_mock.go
Normal file
43
internal/test/router_mock.go
Normal 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
|
||||
}
|
||||
58
internal/test/signer_mock.go
Normal file
58
internal/test/signer_mock.go
Normal 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
|
||||
}
|
||||
98
internal/test/testutils.go
Normal file
98
internal/test/testutils.go
Normal 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
34
internal/test/timeout.go
Normal 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()
|
||||
}
|
||||
}
|
||||
51
internal/test/versioner_mock.go
Normal file
51
internal/test/versioner_mock.go
Normal 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
|
||||
}
|
||||
133
internal/test/walletkit_mock.go
Normal file
133
internal/test/walletkit_mock.go
Normal 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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user