mirror of
https://github.com/lightningnetwork/lnd.git
synced 2024-11-19 01:43:16 +01:00
multi: add zeroconfacceptor that default rejects if no rpc acceptors
This is a safety mechanism so that zero-conf channels are not accepted by default if no rpc acceptor exists.
This commit is contained in:
parent
a1cd7734d8
commit
c2a4a9adbc
@ -23,6 +23,8 @@ func NewChainedAcceptor() *ChainedAcceptor {
|
||||
}
|
||||
|
||||
// AddAcceptor adds a ChannelAcceptor to this ChainedAcceptor.
|
||||
//
|
||||
// NOTE: Part of the MultiplexAcceptor interface.
|
||||
func (c *ChainedAcceptor) AddAcceptor(acceptor ChannelAcceptor) uint64 {
|
||||
id := atomic.AddUint64(&c.acceptorID, 1)
|
||||
|
||||
@ -36,12 +38,22 @@ func (c *ChainedAcceptor) AddAcceptor(acceptor ChannelAcceptor) uint64 {
|
||||
|
||||
// RemoveAcceptor removes a ChannelAcceptor from this ChainedAcceptor given
|
||||
// an ID.
|
||||
//
|
||||
// NOTE: Part of the MultiplexAcceptor interface.
|
||||
func (c *ChainedAcceptor) RemoveAcceptor(id uint64) {
|
||||
c.acceptorsMtx.Lock()
|
||||
delete(c.acceptors, id)
|
||||
c.acceptorsMtx.Unlock()
|
||||
}
|
||||
|
||||
// numAcceptors returns the number of acceptors contained in the
|
||||
// ChainedAcceptor.
|
||||
func (c *ChainedAcceptor) numAcceptors() int {
|
||||
c.acceptorsMtx.RLock()
|
||||
defer c.acceptorsMtx.RUnlock()
|
||||
return len(c.acceptors)
|
||||
}
|
||||
|
||||
// Accept evaluates the results of all ChannelAcceptors in the acceptors map
|
||||
// and returns the conjunction of all these predicates.
|
||||
//
|
||||
@ -91,5 +103,5 @@ func (c *ChainedAcceptor) Accept(req *ChannelAcceptRequest) *ChannelAcceptRespon
|
||||
}
|
||||
|
||||
// A compile-time constraint to ensure ChainedAcceptor implements the
|
||||
// ChannelAcceptor interface.
|
||||
var _ ChannelAcceptor = (*ChainedAcceptor)(nil)
|
||||
// MultiplexAcceptor interface.
|
||||
var _ MultiplexAcceptor = (*ChainedAcceptor)(nil)
|
||||
|
@ -118,3 +118,16 @@ func (c *ChannelAcceptResponse) RejectChannel() bool {
|
||||
type ChannelAcceptor interface {
|
||||
Accept(req *ChannelAcceptRequest) *ChannelAcceptResponse
|
||||
}
|
||||
|
||||
// MultiplexAcceptor is an interface that abstracts the ability of a
|
||||
// ChannelAcceptor to contain sub-ChannelAcceptors.
|
||||
type MultiplexAcceptor interface {
|
||||
// Embed the ChannelAcceptor.
|
||||
ChannelAcceptor
|
||||
|
||||
// AddAcceptor nests a ChannelAcceptor inside the MultiplexAcceptor.
|
||||
AddAcceptor(acceptor ChannelAcceptor) uint64
|
||||
|
||||
// Remove a sub-ChannelAcceptor.
|
||||
RemoveAcceptor(id uint64)
|
||||
}
|
||||
|
68
chanacceptor/zeroconfacceptor.go
Normal file
68
chanacceptor/zeroconfacceptor.go
Normal file
@ -0,0 +1,68 @@
|
||||
package chanacceptor
|
||||
|
||||
import "github.com/lightningnetwork/lnd/lnwire"
|
||||
|
||||
// ZeroConfAcceptor wraps a regular ChainedAcceptor. If no acceptors are in the
|
||||
// ChainedAcceptor, then Accept will reject all channel open requests. This
|
||||
// should only be enabled when the zero-conf feature bit is set and is used to
|
||||
// protect users from a malicious counter-party double-spending the zero-conf
|
||||
// funding tx.
|
||||
type ZeroConfAcceptor struct {
|
||||
chainedAcceptor *ChainedAcceptor
|
||||
}
|
||||
|
||||
// NewZeroConfAcceptor initializes a ZeroConfAcceptor.
|
||||
func NewZeroConfAcceptor() *ZeroConfAcceptor {
|
||||
return &ZeroConfAcceptor{
|
||||
chainedAcceptor: NewChainedAcceptor(),
|
||||
}
|
||||
}
|
||||
|
||||
// AddAcceptor adds a sub-ChannelAcceptor to the internal ChainedAcceptor.
|
||||
func (z *ZeroConfAcceptor) AddAcceptor(acceptor ChannelAcceptor) uint64 {
|
||||
return z.chainedAcceptor.AddAcceptor(acceptor)
|
||||
}
|
||||
|
||||
// RemoveAcceptor removes a sub-ChannelAcceptor from the internal
|
||||
// ChainedAcceptor.
|
||||
func (z *ZeroConfAcceptor) RemoveAcceptor(id uint64) {
|
||||
z.chainedAcceptor.RemoveAcceptor(id)
|
||||
}
|
||||
|
||||
// Accept will deny the channel open request if the internal ChainedAcceptor is
|
||||
// empty. If the internal ChainedAcceptor has any acceptors, then Accept will
|
||||
// instead be called on it.
|
||||
//
|
||||
// NOTE: Part of the ChannelAcceptor interface.
|
||||
func (z *ZeroConfAcceptor) Accept(
|
||||
req *ChannelAcceptRequest) *ChannelAcceptResponse {
|
||||
|
||||
// Alias for less verbosity.
|
||||
channelType := req.OpenChanMsg.ChannelType
|
||||
|
||||
// Check if the channel type sets the zero-conf bit.
|
||||
var zeroConfSet bool
|
||||
|
||||
if channelType != nil {
|
||||
channelFeatures := lnwire.RawFeatureVector(*channelType)
|
||||
zeroConfSet = channelFeatures.IsSet(lnwire.ZeroConfRequired)
|
||||
}
|
||||
|
||||
// If there are no acceptors and the counter-party is requesting a zero
|
||||
// conf channel, reject the attempt.
|
||||
if z.chainedAcceptor.numAcceptors() == 0 && zeroConfSet {
|
||||
// Deny the channel open request.
|
||||
rejectChannel := NewChannelAcceptResponse(
|
||||
false, nil, nil, 0, 0, 0, 0, 0, 0, false,
|
||||
)
|
||||
return rejectChannel
|
||||
}
|
||||
|
||||
// Otherwise, the ChainedAcceptor has sub-acceptors, so call Accept on
|
||||
// it.
|
||||
return z.chainedAcceptor.Accept(req)
|
||||
}
|
||||
|
||||
// A compile-time constraint to ensure ZeroConfAcceptor implements the
|
||||
// MultiplexAcceptor interface.
|
||||
var _ MultiplexAcceptor = (*ZeroConfAcceptor)(nil)
|
83
chanacceptor/zeroconfacceptor_test.go
Normal file
83
chanacceptor/zeroconfacceptor_test.go
Normal file
@ -0,0 +1,83 @@
|
||||
package chanacceptor
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// dummyAcceptor is a ChannelAcceptor that will never return a failure.
|
||||
type dummyAcceptor struct{}
|
||||
|
||||
func (d *dummyAcceptor) Accept(
|
||||
req *ChannelAcceptRequest) *ChannelAcceptResponse {
|
||||
|
||||
return &ChannelAcceptResponse{}
|
||||
}
|
||||
|
||||
// TestZeroConfAcceptorNormal verifies that the ZeroConfAcceptor will let
|
||||
// requests go through for non-zero-conf channels if there are no
|
||||
// sub-acceptors.
|
||||
func TestZeroConfAcceptorNormal(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Create the zero-conf acceptor.
|
||||
zeroAcceptor := NewZeroConfAcceptor()
|
||||
|
||||
// Assert that calling Accept won't return a failure.
|
||||
req := &ChannelAcceptRequest{
|
||||
OpenChanMsg: &lnwire.OpenChannel{},
|
||||
}
|
||||
resp := zeroAcceptor.Accept(req)
|
||||
require.False(t, resp.RejectChannel())
|
||||
|
||||
// Add a dummyAcceptor to the zero-conf acceptor. Assert that Accept
|
||||
// does not return a failure.
|
||||
dummy := &dummyAcceptor{}
|
||||
dummyID := zeroAcceptor.AddAcceptor(dummy)
|
||||
resp = zeroAcceptor.Accept(req)
|
||||
require.False(t, resp.RejectChannel())
|
||||
|
||||
// Remove the dummyAcceptor from the zero-conf acceptor and assert that
|
||||
// Accept doesn't return a failure.
|
||||
zeroAcceptor.RemoveAcceptor(dummyID)
|
||||
resp = zeroAcceptor.Accept(req)
|
||||
require.False(t, resp.RejectChannel())
|
||||
}
|
||||
|
||||
// TestZeroConfAcceptorZC verifies that the ZeroConfAcceptor will fail
|
||||
// zero-conf channel opens unless a sub-acceptor exists.
|
||||
func TestZeroConfAcceptorZC(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Create the zero-conf acceptor.
|
||||
zeroAcceptor := NewZeroConfAcceptor()
|
||||
|
||||
channelType := new(lnwire.ChannelType)
|
||||
*channelType = lnwire.ChannelType(*lnwire.NewRawFeatureVector(
|
||||
lnwire.ZeroConfRequired,
|
||||
))
|
||||
|
||||
// Assert that calling Accept results in failure.
|
||||
req := &ChannelAcceptRequest{
|
||||
OpenChanMsg: &lnwire.OpenChannel{
|
||||
ChannelType: channelType,
|
||||
},
|
||||
}
|
||||
resp := zeroAcceptor.Accept(req)
|
||||
require.True(t, resp.RejectChannel())
|
||||
|
||||
// Add a dummyAcceptor to the zero-conf acceptor. Assert that Accept
|
||||
// does not return a failure.
|
||||
dummy := &dummyAcceptor{}
|
||||
dummyID := zeroAcceptor.AddAcceptor(dummy)
|
||||
resp = zeroAcceptor.Accept(req)
|
||||
require.False(t, resp.RejectChannel())
|
||||
|
||||
// Remove the dummyAcceptor from the zero-conf acceptor and assert that
|
||||
// Accept returns a failure.
|
||||
zeroAcceptor.RemoveAcceptor(dummyID)
|
||||
resp = zeroAcceptor.Accept(req)
|
||||
require.True(t, resp.RejectChannel())
|
||||
}
|
@ -1369,6 +1369,18 @@ func (f *Manager) handleFundingOpen(peer lnpeer.Peer,
|
||||
zeroConf = featureVec.IsSet(lnwire.ZeroConfRequired)
|
||||
scid = featureVec.IsSet(lnwire.ScidAliasRequired)
|
||||
|
||||
// If the zero-conf channel type was negotiated, ensure that
|
||||
// the acceptor allows it.
|
||||
if zeroConf && !acceptorResp.ZeroConf {
|
||||
// Fail the funding flow.
|
||||
flowErr := fmt.Errorf("channel acceptor blocked " +
|
||||
"zero-conf channel negotiation")
|
||||
f.failFundingFlow(
|
||||
peer, msg.PendingChannelID, flowErr,
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
// If the zero-conf channel type wasn't negotiated and the
|
||||
// fundee still wants a zero-conf channel, perform more checks.
|
||||
// Require that both sides have the scid-alias feature bit set.
|
||||
|
@ -25,7 +25,7 @@ import (
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/lightningnetwork/lnd/chainntnfs"
|
||||
"github.com/lightningnetwork/lnd/chainreg"
|
||||
"github.com/lightningnetwork/lnd/chanacceptor"
|
||||
acpt "github.com/lightningnetwork/lnd/chanacceptor"
|
||||
"github.com/lightningnetwork/lnd/channeldb"
|
||||
"github.com/lightningnetwork/lnd/channelnotifier"
|
||||
"github.com/lightningnetwork/lnd/discovery"
|
||||
@ -222,6 +222,19 @@ func (m *mockChanEvent) NotifyPendingOpenChannelEvent(outpoint wire.OutPoint,
|
||||
}
|
||||
}
|
||||
|
||||
// mockZeroConfAcceptor always accepts the channel open request for zero-conf
|
||||
// channels. It will set the ZeroConf bool in the ChannelAcceptResponse. This
|
||||
// is needed to properly unit test the zero-conf logic in the funding manager.
|
||||
type mockZeroConfAcceptor struct{}
|
||||
|
||||
func (m *mockZeroConfAcceptor) Accept(
|
||||
req *acpt.ChannelAcceptRequest) *acpt.ChannelAcceptResponse {
|
||||
|
||||
return &acpt.ChannelAcceptResponse{
|
||||
ZeroConf: true,
|
||||
}
|
||||
}
|
||||
|
||||
type newChannelMsg struct {
|
||||
channel *channeldb.OpenChannel
|
||||
err chan error
|
||||
@ -400,7 +413,7 @@ func createTestFundingManager(t *testing.T, privKey *btcec.PrivateKey,
|
||||
|
||||
var chanIDSeed [32]byte
|
||||
|
||||
chainedAcceptor := chanacceptor.NewChainedAcceptor()
|
||||
chainedAcceptor := acpt.NewChainedAcceptor()
|
||||
|
||||
fundingCfg := Config{
|
||||
IDKey: privKey.PubKey(),
|
||||
@ -556,7 +569,7 @@ func recreateAliceFundingManager(t *testing.T, alice *testNode) {
|
||||
|
||||
oldCfg := alice.fundingMgr.cfg
|
||||
|
||||
chainedAcceptor := chanacceptor.NewChainedAcceptor()
|
||||
chainedAcceptor := acpt.NewChainedAcceptor()
|
||||
|
||||
f, err := NewFundingManager(Config{
|
||||
IDKey: oldCfg.IDKey,
|
||||
@ -3760,6 +3773,11 @@ func TestFundingManagerZeroConf(t *testing.T) {
|
||||
*lnwire.NewRawFeatureVector(channelTypeBits...),
|
||||
)
|
||||
|
||||
// Create a default-accept channelacceptor so that the test passes and
|
||||
// we don't have to use any goroutines.
|
||||
mockAcceptor := &mockZeroConfAcceptor{}
|
||||
bob.fundingMgr.cfg.OpenChannelPredicate = mockAcceptor
|
||||
|
||||
// Call fundChannel with the zero-conf ChannelType.
|
||||
fundingTx := fundChannel(
|
||||
t, alice, bob, fundingAmt, pushAmt, false, 1, updateChan, true,
|
||||
|
15
lnd.go
15
lnd.go
@ -496,15 +496,22 @@ func Main(cfg *Config, lisCfg ListenerCfg, implCfg *ImplementationCfg,
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize the ChainedAcceptor.
|
||||
chainedAcceptor := chanacceptor.NewChainedAcceptor()
|
||||
// Initialize the MultiplexAcceptor. If lnd was started with the
|
||||
// zero-conf feature bit, then this will be a ZeroConfAcceptor.
|
||||
// Otherwise, this will be a ChainedAcceptor.
|
||||
var multiAcceptor chanacceptor.MultiplexAcceptor
|
||||
if cfg.ProtocolOptions.ZeroConf() {
|
||||
multiAcceptor = chanacceptor.NewZeroConfAcceptor()
|
||||
} else {
|
||||
multiAcceptor = chanacceptor.NewChainedAcceptor()
|
||||
}
|
||||
|
||||
// Set up the core server which will listen for incoming peer
|
||||
// connections.
|
||||
server, err := newServer(
|
||||
cfg, cfg.Listeners, dbs, activeChainControl, &idKeyDesc,
|
||||
activeChainControl.Cfg.WalletUnlockParams.ChansToRestore,
|
||||
chainedAcceptor, torController,
|
||||
multiAcceptor, torController,
|
||||
)
|
||||
if err != nil {
|
||||
return mkErr("unable to create server: %v", err)
|
||||
@ -534,7 +541,7 @@ func Main(cfg *Config, lisCfg ListenerCfg, implCfg *ImplementationCfg,
|
||||
// start the RPC server.
|
||||
err = rpcServer.addDeps(
|
||||
server, interceptorChain.MacaroonService(), cfg.SubRPCServers,
|
||||
atplManager, server.invoices, tower, chainedAcceptor,
|
||||
atplManager, server.invoices, tower, multiAcceptor,
|
||||
)
|
||||
if err != nil {
|
||||
return mkErr("unable to add deps to RPC server: %v", err)
|
||||
|
@ -612,7 +612,7 @@ type rpcServer struct {
|
||||
|
||||
// chanPredicate is used in the bidirectional ChannelAcceptor streaming
|
||||
// method.
|
||||
chanPredicate *chanacceptor.ChainedAcceptor
|
||||
chanPredicate chanacceptor.MultiplexAcceptor
|
||||
|
||||
quit chan struct{}
|
||||
|
||||
@ -676,7 +676,7 @@ func newRPCServer(cfg *Config, interceptorChain *rpcperms.InterceptorChain,
|
||||
func (r *rpcServer) addDeps(s *server, macService *macaroons.Service,
|
||||
subServerCgs *subRPCServerConfigs, atpl *autopilot.Manager,
|
||||
invoiceRegistry *invoices.InvoiceRegistry, tower *watchtower.Standalone,
|
||||
chanPredicate *chanacceptor.ChainedAcceptor) error {
|
||||
chanPredicate chanacceptor.MultiplexAcceptor) error {
|
||||
|
||||
// Set up router rpc backend.
|
||||
selfNode, err := s.graphDB.SourceNode()
|
||||
|
Loading…
Reference in New Issue
Block a user