mirror of
https://github.com/lightninglabs/loop
synced 2024-11-16 00:12:52 +00:00
5f6ad1161b
In this commit, we modify all sub-servers as well as the main lnd gRPC service to use a macaroon per call rather than a connection level macaroon. The old approach would result in us using the same macaroon for the entire connection, while this new approach allows us to use multiple macaroons with a single connection. We move to this new approach as it doesn't require users to delete their admin macaroon before being able to use Loop. It also preps us for a future where there is no admin macaroon, and we instead need to use a set of macaroons to accomplish our tasks.
240 lines
5.2 KiB
Go
240 lines
5.2 KiB
Go
package lndclient
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"sync"
|
|
|
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
|
"github.com/btcsuite/btcd/wire"
|
|
"github.com/lightninglabs/loop/swap"
|
|
"github.com/lightningnetwork/lnd/chainntnfs"
|
|
"github.com/lightningnetwork/lnd/lnrpc/chainrpc"
|
|
"google.golang.org/grpc"
|
|
)
|
|
|
|
// ChainNotifierClient exposes base lightning functionality.
|
|
type ChainNotifierClient interface {
|
|
RegisterBlockEpochNtfn(ctx context.Context) (
|
|
chan int32, chan error, error)
|
|
|
|
RegisterConfirmationsNtfn(ctx context.Context, txid *chainhash.Hash,
|
|
pkScript []byte, numConfs, heightHint int32) (
|
|
chan *chainntnfs.TxConfirmation, chan error, error)
|
|
|
|
RegisterSpendNtfn(ctx context.Context,
|
|
outpoint *wire.OutPoint, pkScript []byte, heightHint int32) (
|
|
chan *chainntnfs.SpendDetail, chan error, error)
|
|
}
|
|
|
|
type chainNotifierClient struct {
|
|
client chainrpc.ChainNotifierClient
|
|
chainMac serializedMacaroon
|
|
|
|
wg sync.WaitGroup
|
|
}
|
|
|
|
func newChainNotifierClient(conn *grpc.ClientConn, chainMac serializedMacaroon) *chainNotifierClient {
|
|
return &chainNotifierClient{
|
|
client: chainrpc.NewChainNotifierClient(conn),
|
|
chainMac: chainMac,
|
|
}
|
|
}
|
|
|
|
func (s *chainNotifierClient) WaitForFinished() {
|
|
s.wg.Wait()
|
|
}
|
|
|
|
func (s *chainNotifierClient) RegisterSpendNtfn(ctx context.Context,
|
|
outpoint *wire.OutPoint, pkScript []byte, heightHint int32) (
|
|
chan *chainntnfs.SpendDetail, chan error, error) {
|
|
|
|
var rpcOutpoint *chainrpc.Outpoint
|
|
if outpoint != nil {
|
|
rpcOutpoint = &chainrpc.Outpoint{
|
|
Hash: outpoint.Hash[:],
|
|
Index: outpoint.Index,
|
|
}
|
|
}
|
|
|
|
macaroonAuth := s.chainMac.WithMacaroonAuth(ctx)
|
|
resp, err := s.client.RegisterSpendNtfn(macaroonAuth, &chainrpc.SpendRequest{
|
|
HeightHint: uint32(heightHint),
|
|
Outpoint: rpcOutpoint,
|
|
Script: pkScript,
|
|
})
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
spendChan := make(chan *chainntnfs.SpendDetail, 1)
|
|
errChan := make(chan error, 1)
|
|
|
|
processSpendDetail := func(d *chainrpc.SpendDetails) error {
|
|
outpointHash, err := chainhash.NewHash(d.SpendingOutpoint.Hash)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
txHash, err := chainhash.NewHash(d.SpendingTxHash)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
tx, err := swap.DecodeTx(d.RawSpendingTx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
spendChan <- &chainntnfs.SpendDetail{
|
|
SpentOutPoint: &wire.OutPoint{
|
|
Hash: *outpointHash,
|
|
Index: d.SpendingOutpoint.Index,
|
|
},
|
|
SpenderTxHash: txHash,
|
|
SpenderInputIndex: d.SpendingInputIndex,
|
|
SpendingTx: tx,
|
|
SpendingHeight: int32(d.SpendingHeight),
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
s.wg.Add(1)
|
|
go func() {
|
|
defer s.wg.Done()
|
|
for {
|
|
spendEvent, err := resp.Recv()
|
|
if err != nil {
|
|
errChan <- err
|
|
return
|
|
}
|
|
|
|
switch c := spendEvent.Event.(type) {
|
|
case *chainrpc.SpendEvent_Spend:
|
|
err := processSpendDetail(c.Spend)
|
|
if err != nil {
|
|
errChan <- err
|
|
}
|
|
return
|
|
}
|
|
}
|
|
}()
|
|
|
|
return spendChan, errChan, nil
|
|
}
|
|
|
|
func (s *chainNotifierClient) RegisterConfirmationsNtfn(ctx context.Context,
|
|
txid *chainhash.Hash, pkScript []byte, numConfs, heightHint int32) (
|
|
chan *chainntnfs.TxConfirmation, chan error, error) {
|
|
|
|
var txidSlice []byte
|
|
if txid != nil {
|
|
txidSlice = txid[:]
|
|
}
|
|
confStream, err := s.client.RegisterConfirmationsNtfn(
|
|
s.chainMac.WithMacaroonAuth(ctx),
|
|
&chainrpc.ConfRequest{
|
|
Script: pkScript,
|
|
NumConfs: uint32(numConfs),
|
|
HeightHint: uint32(heightHint),
|
|
Txid: txidSlice,
|
|
},
|
|
)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
confChan := make(chan *chainntnfs.TxConfirmation, 1)
|
|
errChan := make(chan error, 1)
|
|
|
|
s.wg.Add(1)
|
|
go func() {
|
|
defer s.wg.Done()
|
|
|
|
for {
|
|
var confEvent *chainrpc.ConfEvent
|
|
confEvent, err := confStream.Recv()
|
|
if err != nil {
|
|
errChan <- err
|
|
return
|
|
}
|
|
|
|
switch c := confEvent.Event.(type) {
|
|
|
|
// Script confirmed
|
|
case *chainrpc.ConfEvent_Conf:
|
|
tx, err := swap.DecodeTx(c.Conf.RawTx)
|
|
if err != nil {
|
|
errChan <- err
|
|
return
|
|
}
|
|
blockHash, err := chainhash.NewHash(
|
|
c.Conf.BlockHash,
|
|
)
|
|
if err != nil {
|
|
errChan <- err
|
|
return
|
|
}
|
|
confChan <- &chainntnfs.TxConfirmation{
|
|
BlockHeight: c.Conf.BlockHeight,
|
|
BlockHash: blockHash,
|
|
Tx: tx,
|
|
TxIndex: c.Conf.TxIndex,
|
|
}
|
|
return
|
|
|
|
// Ignore reorg events, not supported.
|
|
case *chainrpc.ConfEvent_Reorg:
|
|
continue
|
|
|
|
// Nil event, should never happen.
|
|
case nil:
|
|
errChan <- fmt.Errorf("conf event empty")
|
|
return
|
|
|
|
// Unexpected type.
|
|
default:
|
|
errChan <- fmt.Errorf(
|
|
"conf event has unexpected type",
|
|
)
|
|
return
|
|
}
|
|
}
|
|
}()
|
|
|
|
return confChan, errChan, nil
|
|
}
|
|
|
|
func (s *chainNotifierClient) RegisterBlockEpochNtfn(ctx context.Context) (
|
|
chan int32, chan error, error) {
|
|
|
|
blockEpochClient, err := s.client.RegisterBlockEpochNtfn(
|
|
s.chainMac.WithMacaroonAuth(ctx), &chainrpc.BlockEpoch{},
|
|
)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
blockErrorChan := make(chan error, 1)
|
|
blockEpochChan := make(chan int32)
|
|
|
|
// Start block epoch goroutine.
|
|
s.wg.Add(1)
|
|
go func() {
|
|
defer s.wg.Done()
|
|
for {
|
|
epoch, err := blockEpochClient.Recv()
|
|
if err != nil {
|
|
blockErrorChan <- err
|
|
return
|
|
}
|
|
|
|
select {
|
|
case blockEpochChan <- int32(epoch.Height):
|
|
case <-ctx.Done():
|
|
return
|
|
}
|
|
}
|
|
}()
|
|
|
|
return blockEpochChan, blockErrorChan, nil
|
|
}
|