Skip to content

Commit

Permalink
Merge pull request #98 from positiveblue/lnc
Browse files Browse the repository at this point in the history
[feature] Use LNC to connect to the LND node
  • Loading branch information
Roasbeef authored Jul 5, 2023
2 parents 57ddbf7 + aa9ca46 commit 510d409
Show file tree
Hide file tree
Showing 24 changed files with 1,354 additions and 89 deletions.
99 changes: 80 additions & 19 deletions aperture.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,12 @@ import (
flags "github.com/jessevdk/go-flags"
"github.com/lightninglabs/aperture/aperturedb"
"github.com/lightninglabs/aperture/auth"
"github.com/lightninglabs/aperture/challenger"
"github.com/lightninglabs/aperture/lnc"
"github.com/lightninglabs/aperture/mint"
"github.com/lightninglabs/aperture/proxy"
"github.com/lightninglabs/lightning-node-connect/hashmailrpc"
"github.com/lightninglabs/lndclient"
"github.com/lightningnetwork/lnd"
"github.com/lightningnetwork/lnd/build"
"github.com/lightningnetwork/lnd/cert"
Expand Down Expand Up @@ -75,6 +78,10 @@ const (
// hashMailRESTPrefix is the prefix a REST request URI has when it is
// meant for the hashmailrpc server to be handled.
hashMailRESTPrefix = "/v1/lightning-node-connect/hashmail"

// invoiceMacaroonName is the name of the invoice macaroon belonging
// to the target lnd node.
invoiceMacaroonName = "invoice.macaroon"
)

var (
Expand Down Expand Up @@ -162,7 +169,7 @@ type Aperture struct {

etcdClient *clientv3.Client
db *sql.DB
challenger *LndChallenger
challenger challenger.Challenger
httpsServer *http.Server
torHTTPServer *http.Server
proxy *proxy.Proxy
Expand Down Expand Up @@ -213,6 +220,7 @@ func (a *Aperture) Start(errChan chan error) error {
var (
secretStore mint.SecretStore
onionStore tor.OnionStore
lncStore lnc.Store
)

// Connect to the chosen database backend.
Expand Down Expand Up @@ -254,6 +262,13 @@ func (a *Aperture) Start(errChan chan error) error {
)
onionStore = aperturedb.NewOnionStore(dbOnionTxer)

dbLNCTxer := aperturedb.NewTransactionExecutor(db,
func(tx *sql.Tx) aperturedb.LNCSessionsDB {
return db.WithTx(tx)
},
)
lncStore = aperturedb.NewLNCSessionsStore(dbLNCTxer)

case "sqlite":
db, err := aperturedb.NewSqliteStore(a.cfg.Sqlite)
if err != nil {
Expand All @@ -276,32 +291,78 @@ func (a *Aperture) Start(errChan chan error) error {
)
onionStore = aperturedb.NewOnionStore(dbOnionTxer)

dbLNCTxer := aperturedb.NewTransactionExecutor(db,
func(tx *sql.Tx) aperturedb.LNCSessionsDB {
return db.WithTx(tx)
},
)
lncStore = aperturedb.NewLNCSessionsStore(dbLNCTxer)

default:
return fmt.Errorf("unknown database backend: %s",
a.cfg.DatabaseBackend)
}

log.Infof("Using %v as database backend", a.cfg.DatabaseBackend)

// 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
}

if !a.cfg.Authenticator.Disable {
a.challenger, err = NewLndChallenger(
a.cfg.Authenticator, genInvoiceReq, errChan,
)
if err != nil {
return err
authCfg := a.cfg.Authenticator
genInvoiceReq := func(price int64) (*lnrpc.Invoice, error) {
return &lnrpc.Invoice{
Memo: "LSAT",
Value: price,
}, nil
}
err = a.challenger.Start()
if err != nil {
return err

switch {
case authCfg.Passphrase != "":
log.Infof("Using lnc's authenticator config")

if a.cfg.DatabaseBackend == "etcd" {
return fmt.Errorf("etcd is not supported as " +
"a database backend for lnc " +
"connections")
}

session, err := lnc.NewSession(
authCfg.Passphrase, authCfg.MailboxAddress,
authCfg.DevServer,
)
if err != nil {
return fmt.Errorf("unable to create lnc "+
"session: %w", err)
}

a.challenger, err = challenger.NewLNCChallenger(
session, lncStore, genInvoiceReq, errChan,
)
if err != nil {
return fmt.Errorf("unable to start lnc "+
"challenger: %w", err)
}

case authCfg.LndHost != "":
log.Infof("Using lnd's authenticator config")

authCfg := a.cfg.Authenticator
client, err := lndclient.NewBasicClient(
authCfg.LndHost, authCfg.TLSPath,
authCfg.MacDir, authCfg.Network,
lndclient.MacFilename(
invoiceMacaroonName,
),
)
if err != nil {
return err
}

a.challenger, err = challenger.NewLndChallenger(
client, genInvoiceReq, context.Background,
errChan,
)
if err != nil {
return err
}
}
}

Expand Down Expand Up @@ -738,7 +799,7 @@ func initTorListener(cfg *Config, store tor.OnionStore) (*tor.Controller,
}

// createProxy creates the proxy with all the services it needs.
func createProxy(cfg *Config, challenger *LndChallenger,
func createProxy(cfg *Config, challenger challenger.Challenger,
store mint.SecretStore) (*proxy.Proxy, func(), error) {

minter := mint.New(&mint.Config{
Expand Down
218 changes: 218 additions & 0 deletions aperturedb/lnc_sessions.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
package aperturedb

import (
"context"
"database/sql"
"fmt"
"time"

"github.com/btcsuite/btcd/btcec/v2"
"github.com/lightninglabs/aperture/aperturedb/sqlc"
"github.com/lightninglabs/aperture/lnc"
"github.com/lightningnetwork/lnd/clock"
)

type (
NewLNCSession = sqlc.InsertSessionParams

SetRemoteParams = sqlc.SetRemotePubKeyParams

SetExpiryParams = sqlc.SetExpiryParams
)

// LNCSessionsDB is an interface that defines the set of operations that can be
// executed agaist the lnc sessions database.
type LNCSessionsDB interface {
// InsertLNCSession inserts a new session into the database.
InsertSession(ctx context.Context, arg NewLNCSession) error

// GetLNCSession returns the session tagged with the given passphrase
// entropy.
GetSession(ctx context.Context,
passphraseEntropy []byte) (sqlc.LncSession, error)

// SetRemotePubKey sets the remote public key for the session.
SetRemotePubKey(ctx context.Context,
arg SetRemoteParams) error

// SetExpiry sets the expiry for the session.
SetExpiry(ctx context.Context, arg SetExpiryParams) error
}

// LNCSessionsDBTxOptions defines the set of db txn options the LNCSessionsDB
// understands.
type LNCSessionsDBTxOptions struct {
// readOnly governs if a read only transaction is needed or not.
readOnly bool
}

// ReadOnly returns true if the transaction should be read only.
//
// NOTE: This implements the TxOptions
func (a *LNCSessionsDBTxOptions) ReadOnly() bool {
return a.readOnly
}

// NewLNCSessionsDBReadTx creates a new read transaction option set.
func NewLNCSessionsDBReadTx() LNCSessionsDBTxOptions {
return LNCSessionsDBTxOptions{
readOnly: true,
}
}

// BatchedLNCSessionsDB is a version of the LNCSecretsDB that's capable of
// batched database operations.
type BatchedLNCSessionsDB interface {
LNCSessionsDB

BatchedTx[LNCSessionsDB]
}

// LNCSessionsStore represents a storage backend.
type LNCSessionsStore struct {
db BatchedLNCSessionsDB
clock clock.Clock
}

// NewSecretsStore creates a new SecretsStore instance given a open
// BatchedSecretsDB storage backend.
func NewLNCSessionsStore(db BatchedLNCSessionsDB) *LNCSessionsStore {
return &LNCSessionsStore{
db: db,
clock: clock.NewDefaultClock(),
}
}

// AddSession adds a new session to the database.
func (l *LNCSessionsStore) AddSession(ctx context.Context,
session *lnc.Session) error {

if session.LocalStaticPrivKey == nil {
return fmt.Errorf("local static private key is required")
}

localPrivKey := session.LocalStaticPrivKey.Serialize()
createdAt := l.clock.Now().UTC().Truncate(time.Microsecond)

var writeTxOpts LNCSessionsDBTxOptions
err := l.db.ExecTx(ctx, &writeTxOpts, func(tx LNCSessionsDB) error {
params := sqlc.InsertSessionParams{
PassphraseWords: session.PassphraseWords,
PassphraseEntropy: session.PassphraseEntropy,
LocalStaticPrivKey: localPrivKey,
MailboxAddr: session.MailboxAddr,
CreatedAt: createdAt,
DevServer: session.DevServer,
}

return tx.InsertSession(ctx, params)
})
if err != nil {
return fmt.Errorf("failed to insert new session: %v", err)
}

session.CreatedAt = createdAt

return nil
}

// GetSession returns the session tagged with the given label.
func (l *LNCSessionsStore) GetSession(ctx context.Context,
passphraseEntropy []byte) (*lnc.Session, error) {

var session *lnc.Session

readTx := NewLNCSessionsDBReadTx()
err := l.db.ExecTx(ctx, &readTx, func(tx LNCSessionsDB) error {
dbSession, err := tx.GetSession(ctx, passphraseEntropy)
switch {
case err == sql.ErrNoRows:
return lnc.ErrSessionNotFound

case err != nil:
return err

}

privKey, _ := btcec.PrivKeyFromBytes(
dbSession.LocalStaticPrivKey,
)
session = &lnc.Session{
PassphraseWords: dbSession.PassphraseWords,
PassphraseEntropy: dbSession.PassphraseEntropy,
LocalStaticPrivKey: privKey,
MailboxAddr: dbSession.MailboxAddr,
CreatedAt: dbSession.CreatedAt,
DevServer: dbSession.DevServer,
}

if dbSession.RemoteStaticPubKey != nil {
pubKey, err := btcec.ParsePubKey(
dbSession.RemoteStaticPubKey,
)
if err != nil {
return fmt.Errorf("failed to parse remote "+
"public key for session(%x): %w",
dbSession.PassphraseEntropy, err)
}

session.RemoteStaticPubKey = pubKey
}

if dbSession.Expiry.Valid {
expiry := dbSession.Expiry.Time
session.Expiry = &expiry
}

return nil
})
if err != nil {
return nil, fmt.Errorf("failed to get session: %w", err)
}

return session, nil
}

// SetRemotePubKey sets the remote public key for a session.
func (l *LNCSessionsStore) SetRemotePubKey(ctx context.Context,
passphraseEntropy, remotePubKey []byte) error {

var writeTxOpts LNCSessionsDBTxOptions
err := l.db.ExecTx(ctx, &writeTxOpts, func(tx LNCSessionsDB) error {
params := SetRemoteParams{
PassphraseEntropy: passphraseEntropy,
RemoteStaticPubKey: remotePubKey,
}
return tx.SetRemotePubKey(ctx, params)
})
if err != nil {
return fmt.Errorf("failed to set remote pub key to "+
"session(%x): %w", passphraseEntropy, err)
}

return nil
}

// SetExpiry sets the expiry time for a session.
func (l *LNCSessionsStore) SetExpiry(ctx context.Context,
passphraseEntropy []byte, expiry time.Time) error {

var writeTxOpts LNCSessionsDBTxOptions
err := l.db.ExecTx(ctx, &writeTxOpts, func(tx LNCSessionsDB) error {
params := SetExpiryParams{
PassphraseEntropy: passphraseEntropy,
Expiry: sql.NullTime{
Time: expiry,
Valid: true,
},
}

return tx.SetExpiry(ctx, params)
})
if err != nil {
return fmt.Errorf("failed to set expiry time to session(%x): "+
"%w", passphraseEntropy, err)
}

return nil
}
Loading

0 comments on commit 510d409

Please sign in to comment.