pool/channel_acceptor.go

234 lines
6.2 KiB
Go
Raw Permalink Normal View History

package pool
import (
"context"
"fmt"
"sync"
"github.com/lightninglabs/lndclient"
"github.com/lightninglabs/pool/order"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwire"
)
// ChannelAcceptor is a type that adds an RPC level interceptor for accepting
// channels in lnd. Its main task is to validate the self channel balance (or
// as it's known in the LN lingo: push amount) of incoming channels against the
// expected (and paid for!) amount in the order.
type ChannelAcceptor struct {
lightning lndclient.LightningClient
expectedChans map[[32]byte]*order.Bid
expectedChansMtx sync.Mutex
acceptorCancel func()
errChan chan error
quit chan struct{}
wg sync.WaitGroup
}
// NewChannelAcceptor creates a new channel acceptor with the given lnd client.
func NewChannelAcceptor(lightning lndclient.LightningClient) *ChannelAcceptor {
return &ChannelAcceptor{
lightning: lightning,
expectedChans: make(map[[32]byte]*order.Bid),
quit: make(chan struct{}),
}
}
// Start starts the channel acceptor and subscribes to receive all incoming
// channel events of lnd.
func (s *ChannelAcceptor) Start(errChan chan error) error {
s.errChan = errChan
ctxc := context.Background()
ctxc, s.acceptorCancel = context.WithCancel(ctxc)
rpcErrChan, err := s.lightning.ChannelAcceptor(
ctxc, order.DefaultBatchStepTimeout, s.acceptChannel,
)
if err != nil {
return err
}
s.wg.Add(1)
go s.subscribe(rpcErrChan)
return nil
}
// subscribe subscribes to errors coming from the RPC error channel and forwards
// them to our main error channel.
func (s *ChannelAcceptor) subscribe(rpcErrChan chan error) {
defer s.wg.Done()
for {
select {
case err := <-rpcErrChan:
select {
case s.errChan <- err:
case <-s.quit:
}
case <-s.quit:
s.acceptorCancel()
return
}
}
}
// Stop shuts down the channel acceptor.
func (s *ChannelAcceptor) Stop() {
s.acceptorCancel()
close(s.quit)
s.wg.Wait()
}
// ShimRegistered is a function that should be called whenever a funding shim
// is created for a bid order where we expect an incoming channel at any moment.
func (s *ChannelAcceptor) ShimRegistered(bid *order.Bid, pid [32]byte) {
s.expectedChansMtx.Lock()
defer s.expectedChansMtx.Unlock()
s.expectedChans[pid] = bid
}
// ShimRemoved is a function that should be called whenever a funding shim is
// cleaned up and we no longer expect an incoming channel for it.
func (s *ChannelAcceptor) ShimRemoved(bid *order.Bid) {
s.expectedChansMtx.Lock()
defer s.expectedChansMtx.Unlock()
for pid, expectedBid := range s.expectedChans {
if expectedBid.Nonce() == bid.Nonce() {
delete(s.expectedChans, pid)
}
}
}
// acceptChannel is the callback that is invoked each time a new incoming
// channel message is received in lnd. We inspect it here and if it corresponds
// to a pending channel ID that we have an expectation for, we check whether the
// self chan balance (=push amount) is correct.
func (s *ChannelAcceptor) acceptChannel(_ context.Context,
req *lndclient.AcceptorRequest) (*lndclient.AcceptorResponse, error) {
s.expectedChansMtx.Lock()
defer s.expectedChansMtx.Unlock()
expectedChanBid, ok := s.expectedChans[req.PendingChanID]
// It's not a channel we've registered within the funding manager so we
// just accept it to not interfere with the normal node operation.
if !ok {
return &lndclient.AcceptorResponse{Accept: true}, nil
}
// The push amount in the acceptor request is in milli sats, we need to
// convert it first.
pushAmtSat := lnwire.MilliSatoshi(req.PushAmt).ToSatoshis()
// Push amount must be exactly what we expect. Otherwise the asker could
// be trying to cheat.
if expectedChanBid.SelfChanBalance != pushAmtSat {
return &lndclient.AcceptorResponse{
Accept: false,
Error: fmt.Sprintf("invalid push amount %v",
req.PushAmt),
}, nil
}
switch expectedChanBid.ChannelType {
// The bid doesn't have specific requirements for the channel type.
case order.ChannelTypePeerDependent:
break
// The bid expects a channel type that enforces the channel lease
// maturity in its output scripts.
case order.ChannelTypeScriptEnforced:
if req.CommitmentType == nil {
return &lndclient.AcceptorResponse{
Accept: false,
Error: "expected explicit channel negotiation",
}, nil
}
const expectedType = lnwallet.CommitmentTypeScriptEnforcedLease
if *req.CommitmentType != expectedType {
return &lndclient.AcceptorResponse{
Accept: false,
Error: "expected script enforced channel " +
"lease commitment type",
}, nil
}
case order.ChannelTypeSimpleTaproot:
if req.CommitmentType == nil {
return &lndclient.AcceptorResponse{
Accept: false,
Error: "expected explicit channel negotiation",
}, nil
}
if *req.CommitmentType != lnwallet.CommitmentTypeSimpleTaproot {
return &lndclient.AcceptorResponse{
Accept: false,
Error: "expected simple taproot channel " +
"commitment type",
}, nil
}
default:
log.Warnf("Unhandled channel type %v for bid %v",
expectedChanBid.ChannelType, expectedChanBid.Nonce())
return &lndclient.AcceptorResponse{
Accept: false,
Error: "internal error",
}, nil
}
fundingFlags := lnwire.FundingFlag(req.ChannelFlags)
isPrivateChan := fundingFlags&lnwire.FFAnnounceChannel == 0
// Check that the new channel is announced/unannounced as expected.
if isPrivateChan != expectedChanBid.UnannouncedChannel {
var errMsg string
errTemplate := "expected an %s channel but received an %s one"
if expectedChanBid.UnannouncedChannel {
errMsg = fmt.Sprintf(errTemplate, "unannounced",
"announced")
} else {
errMsg = fmt.Sprintf(errTemplate, "announced",
"unannounced")
}
return &lndclient.AcceptorResponse{
Accept: false,
Error: errMsg,
}, nil
}
// Check that the channel is a zero conf channel if we were expecting
// one.
if expectedChanBid.ZeroConfChannel {
if !req.WantsZeroConf {
return &lndclient.AcceptorResponse{
Accept: false,
Error: "expected zero conf channel",
}, nil
}
return &lndclient.AcceptorResponse{
Accept: true,
MinAcceptDepth: 0,
ZeroConf: true,
}, nil
}
return &lndclient.AcceptorResponse{
Accept: true,
}, nil
}