Skip to content

Commit 510d409

Browse files
authored
Merge pull request #98 from positiveblue/lnc
[feature] Use LNC to connect to the LND node
2 parents 57ddbf7 + aa9ca46 commit 510d409

24 files changed

+1354
-89
lines changed

aperture.go

Lines changed: 80 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,12 @@ import (
2121
flags "github.com/jessevdk/go-flags"
2222
"github.com/lightninglabs/aperture/aperturedb"
2323
"github.com/lightninglabs/aperture/auth"
24+
"github.com/lightninglabs/aperture/challenger"
25+
"github.com/lightninglabs/aperture/lnc"
2426
"github.com/lightninglabs/aperture/mint"
2527
"github.com/lightninglabs/aperture/proxy"
2628
"github.com/lightninglabs/lightning-node-connect/hashmailrpc"
29+
"github.com/lightninglabs/lndclient"
2730
"github.com/lightningnetwork/lnd"
2831
"github.com/lightningnetwork/lnd/build"
2932
"github.com/lightningnetwork/lnd/cert"
@@ -75,6 +78,10 @@ const (
7578
// hashMailRESTPrefix is the prefix a REST request URI has when it is
7679
// meant for the hashmailrpc server to be handled.
7780
hashMailRESTPrefix = "/v1/lightning-node-connect/hashmail"
81+
82+
// invoiceMacaroonName is the name of the invoice macaroon belonging
83+
// to the target lnd node.
84+
invoiceMacaroonName = "invoice.macaroon"
7885
)
7986

8087
var (
@@ -162,7 +169,7 @@ type Aperture struct {
162169

163170
etcdClient *clientv3.Client
164171
db *sql.DB
165-
challenger *LndChallenger
172+
challenger challenger.Challenger
166173
httpsServer *http.Server
167174
torHTTPServer *http.Server
168175
proxy *proxy.Proxy
@@ -213,6 +220,7 @@ func (a *Aperture) Start(errChan chan error) error {
213220
var (
214221
secretStore mint.SecretStore
215222
onionStore tor.OnionStore
223+
lncStore lnc.Store
216224
)
217225

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

265+
dbLNCTxer := aperturedb.NewTransactionExecutor(db,
266+
func(tx *sql.Tx) aperturedb.LNCSessionsDB {
267+
return db.WithTx(tx)
268+
},
269+
)
270+
lncStore = aperturedb.NewLNCSessionsStore(dbLNCTxer)
271+
257272
case "sqlite":
258273
db, err := aperturedb.NewSqliteStore(a.cfg.Sqlite)
259274
if err != nil {
@@ -276,32 +291,78 @@ func (a *Aperture) Start(errChan chan error) error {
276291
)
277292
onionStore = aperturedb.NewOnionStore(dbOnionTxer)
278293

294+
dbLNCTxer := aperturedb.NewTransactionExecutor(db,
295+
func(tx *sql.Tx) aperturedb.LNCSessionsDB {
296+
return db.WithTx(tx)
297+
},
298+
)
299+
lncStore = aperturedb.NewLNCSessionsStore(dbLNCTxer)
300+
279301
default:
280302
return fmt.Errorf("unknown database backend: %s",
281303
a.cfg.DatabaseBackend)
282304
}
283305

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

286-
// Create our challenger that uses our backing lnd node to create
287-
// invoices and check their settlement status.
288-
genInvoiceReq := func(price int64) (*lnrpc.Invoice, error) {
289-
return &lnrpc.Invoice{
290-
Memo: "LSAT",
291-
Value: price,
292-
}, nil
293-
}
294-
295308
if !a.cfg.Authenticator.Disable {
296-
a.challenger, err = NewLndChallenger(
297-
a.cfg.Authenticator, genInvoiceReq, errChan,
298-
)
299-
if err != nil {
300-
return err
309+
authCfg := a.cfg.Authenticator
310+
genInvoiceReq := func(price int64) (*lnrpc.Invoice, error) {
311+
return &lnrpc.Invoice{
312+
Memo: "LSAT",
313+
Value: price,
314+
}, nil
301315
}
302-
err = a.challenger.Start()
303-
if err != nil {
304-
return err
316+
317+
switch {
318+
case authCfg.Passphrase != "":
319+
log.Infof("Using lnc's authenticator config")
320+
321+
if a.cfg.DatabaseBackend == "etcd" {
322+
return fmt.Errorf("etcd is not supported as " +
323+
"a database backend for lnc " +
324+
"connections")
325+
}
326+
327+
session, err := lnc.NewSession(
328+
authCfg.Passphrase, authCfg.MailboxAddress,
329+
authCfg.DevServer,
330+
)
331+
if err != nil {
332+
return fmt.Errorf("unable to create lnc "+
333+
"session: %w", err)
334+
}
335+
336+
a.challenger, err = challenger.NewLNCChallenger(
337+
session, lncStore, genInvoiceReq, errChan,
338+
)
339+
if err != nil {
340+
return fmt.Errorf("unable to start lnc "+
341+
"challenger: %w", err)
342+
}
343+
344+
case authCfg.LndHost != "":
345+
log.Infof("Using lnd's authenticator config")
346+
347+
authCfg := a.cfg.Authenticator
348+
client, err := lndclient.NewBasicClient(
349+
authCfg.LndHost, authCfg.TLSPath,
350+
authCfg.MacDir, authCfg.Network,
351+
lndclient.MacFilename(
352+
invoiceMacaroonName,
353+
),
354+
)
355+
if err != nil {
356+
return err
357+
}
358+
359+
a.challenger, err = challenger.NewLndChallenger(
360+
client, genInvoiceReq, context.Background,
361+
errChan,
362+
)
363+
if err != nil {
364+
return err
365+
}
305366
}
306367
}
307368

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

740801
// createProxy creates the proxy with all the services it needs.
741-
func createProxy(cfg *Config, challenger *LndChallenger,
802+
func createProxy(cfg *Config, challenger challenger.Challenger,
742803
store mint.SecretStore) (*proxy.Proxy, func(), error) {
743804

744805
minter := mint.New(&mint.Config{

aperturedb/lnc_sessions.go

Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
package aperturedb
2+
3+
import (
4+
"context"
5+
"database/sql"
6+
"fmt"
7+
"time"
8+
9+
"github.com/btcsuite/btcd/btcec/v2"
10+
"github.com/lightninglabs/aperture/aperturedb/sqlc"
11+
"github.com/lightninglabs/aperture/lnc"
12+
"github.com/lightningnetwork/lnd/clock"
13+
)
14+
15+
type (
16+
NewLNCSession = sqlc.InsertSessionParams
17+
18+
SetRemoteParams = sqlc.SetRemotePubKeyParams
19+
20+
SetExpiryParams = sqlc.SetExpiryParams
21+
)
22+
23+
// LNCSessionsDB is an interface that defines the set of operations that can be
24+
// executed agaist the lnc sessions database.
25+
type LNCSessionsDB interface {
26+
// InsertLNCSession inserts a new session into the database.
27+
InsertSession(ctx context.Context, arg NewLNCSession) error
28+
29+
// GetLNCSession returns the session tagged with the given passphrase
30+
// entropy.
31+
GetSession(ctx context.Context,
32+
passphraseEntropy []byte) (sqlc.LncSession, error)
33+
34+
// SetRemotePubKey sets the remote public key for the session.
35+
SetRemotePubKey(ctx context.Context,
36+
arg SetRemoteParams) error
37+
38+
// SetExpiry sets the expiry for the session.
39+
SetExpiry(ctx context.Context, arg SetExpiryParams) error
40+
}
41+
42+
// LNCSessionsDBTxOptions defines the set of db txn options the LNCSessionsDB
43+
// understands.
44+
type LNCSessionsDBTxOptions struct {
45+
// readOnly governs if a read only transaction is needed or not.
46+
readOnly bool
47+
}
48+
49+
// ReadOnly returns true if the transaction should be read only.
50+
//
51+
// NOTE: This implements the TxOptions
52+
func (a *LNCSessionsDBTxOptions) ReadOnly() bool {
53+
return a.readOnly
54+
}
55+
56+
// NewLNCSessionsDBReadTx creates a new read transaction option set.
57+
func NewLNCSessionsDBReadTx() LNCSessionsDBTxOptions {
58+
return LNCSessionsDBTxOptions{
59+
readOnly: true,
60+
}
61+
}
62+
63+
// BatchedLNCSessionsDB is a version of the LNCSecretsDB that's capable of
64+
// batched database operations.
65+
type BatchedLNCSessionsDB interface {
66+
LNCSessionsDB
67+
68+
BatchedTx[LNCSessionsDB]
69+
}
70+
71+
// LNCSessionsStore represents a storage backend.
72+
type LNCSessionsStore struct {
73+
db BatchedLNCSessionsDB
74+
clock clock.Clock
75+
}
76+
77+
// NewSecretsStore creates a new SecretsStore instance given a open
78+
// BatchedSecretsDB storage backend.
79+
func NewLNCSessionsStore(db BatchedLNCSessionsDB) *LNCSessionsStore {
80+
return &LNCSessionsStore{
81+
db: db,
82+
clock: clock.NewDefaultClock(),
83+
}
84+
}
85+
86+
// AddSession adds a new session to the database.
87+
func (l *LNCSessionsStore) AddSession(ctx context.Context,
88+
session *lnc.Session) error {
89+
90+
if session.LocalStaticPrivKey == nil {
91+
return fmt.Errorf("local static private key is required")
92+
}
93+
94+
localPrivKey := session.LocalStaticPrivKey.Serialize()
95+
createdAt := l.clock.Now().UTC().Truncate(time.Microsecond)
96+
97+
var writeTxOpts LNCSessionsDBTxOptions
98+
err := l.db.ExecTx(ctx, &writeTxOpts, func(tx LNCSessionsDB) error {
99+
params := sqlc.InsertSessionParams{
100+
PassphraseWords: session.PassphraseWords,
101+
PassphraseEntropy: session.PassphraseEntropy,
102+
LocalStaticPrivKey: localPrivKey,
103+
MailboxAddr: session.MailboxAddr,
104+
CreatedAt: createdAt,
105+
DevServer: session.DevServer,
106+
}
107+
108+
return tx.InsertSession(ctx, params)
109+
})
110+
if err != nil {
111+
return fmt.Errorf("failed to insert new session: %v", err)
112+
}
113+
114+
session.CreatedAt = createdAt
115+
116+
return nil
117+
}
118+
119+
// GetSession returns the session tagged with the given label.
120+
func (l *LNCSessionsStore) GetSession(ctx context.Context,
121+
passphraseEntropy []byte) (*lnc.Session, error) {
122+
123+
var session *lnc.Session
124+
125+
readTx := NewLNCSessionsDBReadTx()
126+
err := l.db.ExecTx(ctx, &readTx, func(tx LNCSessionsDB) error {
127+
dbSession, err := tx.GetSession(ctx, passphraseEntropy)
128+
switch {
129+
case err == sql.ErrNoRows:
130+
return lnc.ErrSessionNotFound
131+
132+
case err != nil:
133+
return err
134+
135+
}
136+
137+
privKey, _ := btcec.PrivKeyFromBytes(
138+
dbSession.LocalStaticPrivKey,
139+
)
140+
session = &lnc.Session{
141+
PassphraseWords: dbSession.PassphraseWords,
142+
PassphraseEntropy: dbSession.PassphraseEntropy,
143+
LocalStaticPrivKey: privKey,
144+
MailboxAddr: dbSession.MailboxAddr,
145+
CreatedAt: dbSession.CreatedAt,
146+
DevServer: dbSession.DevServer,
147+
}
148+
149+
if dbSession.RemoteStaticPubKey != nil {
150+
pubKey, err := btcec.ParsePubKey(
151+
dbSession.RemoteStaticPubKey,
152+
)
153+
if err != nil {
154+
return fmt.Errorf("failed to parse remote "+
155+
"public key for session(%x): %w",
156+
dbSession.PassphraseEntropy, err)
157+
}
158+
159+
session.RemoteStaticPubKey = pubKey
160+
}
161+
162+
if dbSession.Expiry.Valid {
163+
expiry := dbSession.Expiry.Time
164+
session.Expiry = &expiry
165+
}
166+
167+
return nil
168+
})
169+
if err != nil {
170+
return nil, fmt.Errorf("failed to get session: %w", err)
171+
}
172+
173+
return session, nil
174+
}
175+
176+
// SetRemotePubKey sets the remote public key for a session.
177+
func (l *LNCSessionsStore) SetRemotePubKey(ctx context.Context,
178+
passphraseEntropy, remotePubKey []byte) error {
179+
180+
var writeTxOpts LNCSessionsDBTxOptions
181+
err := l.db.ExecTx(ctx, &writeTxOpts, func(tx LNCSessionsDB) error {
182+
params := SetRemoteParams{
183+
PassphraseEntropy: passphraseEntropy,
184+
RemoteStaticPubKey: remotePubKey,
185+
}
186+
return tx.SetRemotePubKey(ctx, params)
187+
})
188+
if err != nil {
189+
return fmt.Errorf("failed to set remote pub key to "+
190+
"session(%x): %w", passphraseEntropy, err)
191+
}
192+
193+
return nil
194+
}
195+
196+
// SetExpiry sets the expiry time for a session.
197+
func (l *LNCSessionsStore) SetExpiry(ctx context.Context,
198+
passphraseEntropy []byte, expiry time.Time) error {
199+
200+
var writeTxOpts LNCSessionsDBTxOptions
201+
err := l.db.ExecTx(ctx, &writeTxOpts, func(tx LNCSessionsDB) error {
202+
params := SetExpiryParams{
203+
PassphraseEntropy: passphraseEntropy,
204+
Expiry: sql.NullTime{
205+
Time: expiry,
206+
Valid: true,
207+
},
208+
}
209+
210+
return tx.SetExpiry(ctx, params)
211+
})
212+
if err != nil {
213+
return fmt.Errorf("failed to set expiry time to session(%x): "+
214+
"%w", passphraseEntropy, err)
215+
}
216+
217+
return nil
218+
}

0 commit comments

Comments
 (0)