Merge pull request #4567 from calvinrzachman/max-wumbo

add new max channel size config option
This commit is contained in:
Olaoluwa Osuntokun 2020-09-15 15:31:47 -07:00 committed by GitHub
commit fa342a1230
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 272 additions and 7 deletions

@ -248,6 +248,7 @@ type Config struct {
Alias string `long:"alias" description:"The node alias. Used as a moniker by peers and intelligence services"`
Color string `long:"color" description:"The color of the node in hex format (i.e. '#3399FF'). Used to customize node appearance in intelligence services"`
MinChanSize int64 `long:"minchansize" description:"The smallest channel size (in satoshis) that we should accept. Incoming channels smaller than this will be rejected"`
MaxChanSize int64 `long:"maxchansize" description:"The largest channel size (in satoshis) that we should accept. Incoming channels larger than this will be rejected"`
DefaultRemoteMaxHtlcs uint16 `long:"default-remote-max-htlcs" description:"The default max_htlc applied when opening or accepting channels. This value limits the number of concurrent HTLCs that the remote party can add to the commitment. The maximum possible value is 483."`
@ -397,6 +398,7 @@ func DefaultConfig() Config {
Alias: defaultAlias,
Color: defaultColor,
MinChanSize: int64(minChanFundingSize),
MaxChanSize: int64(0),
DefaultRemoteMaxHtlcs: defaultRemoteMaxHtlcs,
NumGraphSyncPeers: defaultMinPeers,
HistoricalSyncInterval: discovery.DefaultHistoricalSyncInterval,
@ -624,7 +626,7 @@ func ValidateConfig(cfg Config, usageMessage string) (*Config, error) {
}
// Ensure that the specified values for the min and max channel size
// don't are within the bounds of the normal chan size constraints.
// are within the bounds of the normal chan size constraints.
if cfg.Autopilot.MinChannelSize < int64(minChanFundingSize) {
cfg.Autopilot.MinChannelSize = int64(minChanFundingSize)
}
@ -636,6 +638,38 @@ func ValidateConfig(cfg Config, usageMessage string) (*Config, error) {
return nil, err
}
// Ensure that --maxchansize is properly handled when set by user.
// For non-Wumbo channels this limit remains 16777215 satoshis by default
// as specified in BOLT-02. For wumbo channels this limit is 1,000,000,000.
// satoshis (10 BTC). Always enforce --maxchansize explicitly set by user.
// If unset (marked by 0 value), then enforce proper default.
if cfg.MaxChanSize == 0 {
if cfg.ProtocolOptions.Wumbo() {
cfg.MaxChanSize = int64(MaxBtcFundingAmountWumbo)
} else {
cfg.MaxChanSize = int64(MaxBtcFundingAmount)
}
}
// Ensure that the user specified values for the min and max channel
// size make sense.
if cfg.MaxChanSize < cfg.MinChanSize {
return nil, fmt.Errorf("invalid channel size parameters: "+
"max channel size %v, must be no less than min chan size %v",
cfg.MaxChanSize, cfg.MinChanSize,
)
}
// Don't allow superflous --maxchansize greater than
// BOLT 02 soft-limit for non-wumbo channel
if !cfg.ProtocolOptions.Wumbo() && cfg.MaxChanSize > int64(MaxFundingAmount) {
return nil, fmt.Errorf("invalid channel size parameters: "+
"maximum channel size %v is greater than maximum non-wumbo"+
" channel size %v",
cfg.MaxChanSize, MaxFundingAmount,
)
}
// Ensure a valid max channel fee allocation was set.
if cfg.MaxChannelFeeAllocation <= 0 || cfg.MaxChannelFeeAllocation > 1 {
return nil, fmt.Errorf("invalid max channel fee allocation: "+

@ -67,6 +67,11 @@ const (
// in the real world.
MaxBtcFundingAmount = btcutil.Amount(1<<24) - 1
// MaxBtcFundingAmountWumbo is a soft-limit on the maximum size of wumbo
// channels. This limit is 10 BTC and is the only thing standing between
// you and limitless channel size (apart from 21 million cap)
MaxBtcFundingAmountWumbo = btcutil.Amount(1000000000)
// maxLtcFundingAmount is a soft-limit of the maximum channel size
// currently accepted on the Litecoin chain within the Lightning
// Protocol.
@ -360,6 +365,11 @@ type fundingConfig struct {
// due to fees.
MinChanSize btcutil.Amount
// MaxChanSize is the largest channel size that we'll accept as an
// inbound channel. We have such a parameter, so that you may decide how
// WUMBO you would like your channel.
MaxChanSize btcutil.Amount
// MaxPendingChannels is the maximum number of pending channels we
// allow for each peer.
MaxPendingChannels int
@ -1269,13 +1279,11 @@ func (f *fundingManager) handleFundingOpen(fmsg *fundingOpenMsg) {
return
}
// We'll reject any request to create a channel that's above the
// current soft-limit for channel size, but only if we're rejecting all
// wumbo channel initiations.
if f.cfg.NoWumboChans && msg.FundingAmount > MaxFundingAmount {
// Ensure that the remote party respects our maximum channel size.
if amt > f.cfg.MaxChanSize {
f.failFundingFlow(
fmsg.peer, fmsg.msg.PendingChannelID,
lnwire.ErrChanTooLarge,
lnwallet.ErrChanTooLarge(amt, f.cfg.MaxChanSize),
)
return
}

@ -432,6 +432,7 @@ func createTestFundingManager(t *testing.T, privKey *btcec.PrivateKey,
},
ZombieSweeperInterval: 1 * time.Hour,
ReservationTimeout: 1 * time.Nanosecond,
MaxChanSize: MaxFundingAmount,
MaxPendingChannels: lncfg.DefaultMaxPendingChannels,
NotifyOpenChannelEvent: evt.NotifyOpenChannelEvent,
OpenChannelPredicate: chainedAcceptor,
@ -3212,6 +3213,75 @@ func expectOpenChannelMsg(t *testing.T, msgChan chan lnwire.Message) *lnwire.Ope
return openChannelReq
}
func TestMaxChannelSizeConfig(t *testing.T) {
t.Parallel()
// Create a set of funding managers that will reject wumbo
// channels but set --maxchansize explicitly lower than soft-limit.
// Verify that wumbo rejecting funding managers will respect --maxchansize
// below 16777215 satoshi (MaxFundingAmount) limit.
alice, bob := setupFundingManagers(t, func(cfg *fundingConfig) {
cfg.NoWumboChans = true
cfg.MaxChanSize = MaxFundingAmount - 1
})
// Attempt to create a channel above the limit
// imposed by --maxchansize, which should be rejected.
updateChan := make(chan *lnrpc.OpenStatusUpdate)
errChan := make(chan error, 1)
initReq := &openChanReq{
targetPubkey: bob.privKey.PubKey(),
chainHash: *fundingNetParams.GenesisHash,
localFundingAmt: MaxFundingAmount,
pushAmt: lnwire.NewMSatFromSatoshis(0),
private: false,
updates: updateChan,
err: errChan,
}
// After processing the funding open message, bob should respond with
// an error rejecting the channel that exceeds size limit.
alice.fundingMgr.initFundingWorkflow(bob, initReq)
openChanMsg := expectOpenChannelMsg(t, alice.msgChan)
bob.fundingMgr.processFundingOpen(openChanMsg, alice)
assertErrorSent(t, bob.msgChan)
// Create a set of funding managers that will reject wumbo
// channels but set --maxchansize explicitly higher than soft-limit
// A --maxchansize greater than this limit should have no effect.
tearDownFundingManagers(t, alice, bob)
alice, bob = setupFundingManagers(t, func(cfg *fundingConfig) {
cfg.NoWumboChans = true
cfg.MaxChanSize = MaxFundingAmount + 1
})
// We expect Bob to respond with an Accept channel message.
alice.fundingMgr.initFundingWorkflow(bob, initReq)
openChanMsg = expectOpenChannelMsg(t, alice.msgChan)
bob.fundingMgr.processFundingOpen(openChanMsg, alice)
assertFundingMsgSent(t, bob.msgChan, "AcceptChannel")
// Verify that wumbo accepting funding managers will respect --maxchansize
// Create the funding managers, this time allowing
// wumbo channels but setting --maxchansize explicitly.
tearDownFundingManagers(t, alice, bob)
alice, bob = setupFundingManagers(t, func(cfg *fundingConfig) {
cfg.NoWumboChans = false
cfg.MaxChanSize = btcutil.Amount(100000000)
})
// Attempt to create a channel above the limit
// imposed by --maxchansize, which should be rejected.
initReq.localFundingAmt = btcutil.SatoshiPerBitcoin + 1
// After processing the funding open message, bob should respond with
// an error rejecting the channel that exceeds size limit.
alice.fundingMgr.initFundingWorkflow(bob, initReq)
openChanMsg = expectOpenChannelMsg(t, alice.msgChan)
bob.fundingMgr.processFundingOpen(openChanMsg, alice)
assertErrorSent(t, bob.msgChan)
}
// TestWumboChannelConfig tests that the funding manager will respect the wumbo
// channel config param when creating or accepting new channels.
func TestWumboChannelConfig(t *testing.T) {
@ -3260,6 +3330,7 @@ func TestWumboChannelConfig(t *testing.T) {
tearDownFundingManagers(t, alice, bob)
alice, bob = setupFundingManagers(t, func(cfg *fundingConfig) {
cfg.NoWumboChans = false
cfg.MaxChanSize = MaxBtcFundingAmountWumbo
})
// We should now be able to initiate a wumbo channel funding w/o any

@ -0,0 +1,120 @@
// +build rpctest
package itest
import (
"context"
"fmt"
"strings"
"github.com/btcsuite/btcutil"
"github.com/lightningnetwork/lnd"
"github.com/lightningnetwork/lnd/lntest"
)
// testMaxChannelSize tests that lnd handles --maxchansize parameter
// correctly. Wumbo nodes should enforce a default soft limit of 10 BTC by
// default. This limit can be adjusted with --maxchansize config option
func testMaxChannelSize(net *lntest.NetworkHarness, t *harnessTest) {
// We'll make two new nodes, both wumbo but with the default
// limit on maximum channel size (10 BTC)
wumboNode, err := net.NewNode(
"wumbo", []string{"--protocol.wumbo-channels"},
)
if err != nil {
t.Fatalf("unable to create new node: %v", err)
}
defer shutdownAndAssert(net, t, wumboNode)
wumboNode2, err := net.NewNode(
"wumbo2", []string{"--protocol.wumbo-channels"},
)
if err != nil {
t.Fatalf("unable to create new node: %v", err)
}
defer shutdownAndAssert(net, t, wumboNode2)
// We'll send 11 BTC to the wumbo node so it can test the wumbo soft limit.
ctxb := context.Background()
err = net.SendCoins(ctxb, 11*btcutil.SatoshiPerBitcoin, wumboNode)
if err != nil {
t.Fatalf("unable to send coins to wumbo node: %v", err)
}
// Next we'll connect both nodes, then attempt to make a wumbo channel
// funding request, which should fail as it exceeds the default wumbo
// soft limit of 10 BTC.
err = net.EnsureConnected(ctxb, wumboNode, wumboNode2)
if err != nil {
t.Fatalf("unable to connect peers: %v", err)
}
chanAmt := lnd.MaxBtcFundingAmountWumbo + 1
_, err = net.OpenChannel(
ctxb, wumboNode, wumboNode2, lntest.OpenChannelParams{
Amt: chanAmt,
},
)
if err == nil {
t.Fatalf("expected channel funding to fail as it exceeds 10 BTC limit")
}
// The test should show failure due to the channel exceeding our max size.
if !strings.Contains(err.Error(), "exceeds maximum chan size") {
t.Fatalf("channel should be rejected due to size, instead "+
"error was: %v", err)
}
// Next we'll create a non-wumbo node to verify that it enforces the
// BOLT-02 channel size limit and rejects our funding request.
miniNode, err := net.NewNode("mini", nil)
if err != nil {
t.Fatalf("unable to create new node: %v", err)
}
defer shutdownAndAssert(net, t, miniNode)
err = net.EnsureConnected(ctxb, wumboNode, miniNode)
if err != nil {
t.Fatalf("unable to connect peers: %v", err)
}
_, err = net.OpenChannel(
ctxb, wumboNode, miniNode, lntest.OpenChannelParams{
Amt: chanAmt,
},
)
if err == nil {
t.Fatalf("expected channel funding to fail as it exceeds 0.16 BTC limit")
}
// The test should show failure due to the channel exceeding our max size.
if !strings.Contains(err.Error(), "exceeds maximum chan size") {
t.Fatalf("channel should be rejected due to size, instead "+
"error was: %v", err)
}
// We'll now make another wumbo node with appropriate maximum channel size
// to accept our wumbo channel funding.
wumboNode3, err := net.NewNode(
"wumbo3", []string{"--protocol.wumbo-channels",
fmt.Sprintf("--maxchansize=%v", int64(lnd.MaxBtcFundingAmountWumbo+1))},
)
if err != nil {
t.Fatalf("unable to create new node: %v", err)
}
defer shutdownAndAssert(net, t, wumboNode3)
// Creating a wumbo channel between these two nodes should succeed.
err = net.EnsureConnected(ctxb, wumboNode, wumboNode3)
if err != nil {
t.Fatalf("unable to connect peers: %v", err)
}
chanPoint := openChannelAndAssert(
ctxb, t, net, wumboNode, wumboNode3,
lntest.OpenChannelParams{
Amt: chanAmt,
},
)
closeChannelAndAssert(ctxb, t, net, wumboNode, chanPoint, false)
}

@ -14374,6 +14374,10 @@ var testsCases = []*testCase{
name: "wumbo channels",
test: testWumboChannels,
},
{
name: "maximum channel size",
test: testMaxChannelSize,
},
}
// TestLightningNetworkDaemon performs a series of integration tests amongst a

@ -62,7 +62,7 @@ func testWumboChannels(net *lntest.NetworkHarness, t *harnessTest) {
// The test should indicate a failure due to the channel being too
// large.
if !strings.Contains(err.Error(), "channel too large") {
if !strings.Contains(err.Error(), "exceeds maximum chan size") {
t.Fatalf("channel should be rejected due to size, instead "+
"error was: %v", err)
}

@ -203,6 +203,15 @@
<time> [ERR] FNDG: received funding error from <hex>: chan_id=<hex>, err=channel too large
<time> [ERR] RPCS: [/lnrpc.Lightning/OpenChannel]: received funding error from <hex>: chan_id=<hex>, err=channel too large
<time> [ERR] RPCS: unable to open channel to NodeKey(<hex>): received funding error from <hex>: chan_id=<hex>, err=channel too large
<time> [ERR] FNDG: received funding error from <hex>: chan_id=<hex>, err=chan size of 0.16777216 BTC exceeds maximum chan size of 0.16777215 BTC
<time> [ERR] RPCS: [/lnrpc.Lightning/OpenChannel]: received funding error from <hex>: chan_id=<hex>, err=chan size of 0.16777216 BTC exceeds maximum chan size of 0.16777215 BTC
<time> [ERR] RPCS: unable to open channel to NodeKey(<hex>): received funding error from <hex>: chan_id=<hex>, err=chan size of 0.16777216 BTC exceeds maximum chan size of 0.16777215 BTC
<time> [ERR] FNDG: received funding error from <hex>: chan_id=<hex>, err=chan size of 10.00000001 BTC exceeds maximum chan size of 10 BTC
<time> [ERR] RPCS: [/lnrpc.Lightning/OpenChannel]: received funding error from <hex>: chan_id=<hex>, err=chan size of 10.00000001 BTC exceeds maximum chan size of 10 BTC
<time> [ERR] RPCS: unable to open channel to NodeKey(<hex>): received funding error from <hex>: chan_id=<hex>, err=chan size of 10.00000001 BTC exceeds maximum chan size of 10 BTC
<time> [ERR] FNDG: received funding error from <hex>: chan_id=<hex>, err=chan size of 10.00000001 BTC exceeds maximum chan size of 0.16777215 BTC
<time> [ERR] RPCS: [/lnrpc.Lightning/OpenChannel]: received funding error from <hex>: chan_id=<hex>, err=chan size of 10.00000001 BTC exceeds maximum chan size of 0.16777215 BTC
<time> [ERR] RPCS: unable to open channel to NodeKey(<hex>): received funding error from <hex>: chan_id=<hex>, err=chan size of 10.00000001 BTC exceeds maximum chan size of 0.16777215 BTC
<time> [ERR] NTNF: unable to get hash from block with height <height>
<time> [ERR] FNDG: Unable to add new channel <chan_point> with peer <hex>: canceled adding new channel
<time> [ERR] RPCS: WS: error closing upgraded conn: write tcp4 <ip>-><ip>: write: connection reset by peer

@ -142,6 +142,16 @@ func ErrChanTooSmall(chanSize, minChanSize btcutil.Amount) ReservationError {
}
}
// ErrChanTooLarge returns an error indicating that an incoming channel request
// was too large. We'll reject any incoming channels if they're above our
// configured value for the max channel size we'll accept.
func ErrChanTooLarge(chanSize, maxChanSize btcutil.Amount) ReservationError {
return ReservationError{
fmt.Errorf("chan size of %v exceeds maximum chan size of %v",
chanSize, maxChanSize),
}
}
// ErrHtlcIndexAlreadyFailed is returned when the HTLC index has already been
// failed, but has not been committed by our commitment state.
type ErrHtlcIndexAlreadyFailed uint64

@ -185,6 +185,14 @@
; channels smaller than this will be rejected, default value 20000.
; minchansize=
; The largest channel size (in satoshis) that we should accept. Incoming
; channels larger than this will be rejected. For non-Wumbo channels this
; limit remains 16777215 satoshis by default as specified in BOLT-0002.
; For wumbo channels this limit is 1,000,000,000 satoshis (10 BTC).
; Set this config option explicitly to restrict your maximum channel size
; to better align with your risk tolerance
; maxchansize=
; The duration that a peer connection must be stable before attempting to send a
; channel update to reenable or cancel a pending disables of the peer's channels
; on the network. (default: 19m0s)

@ -1168,6 +1168,7 @@ func newServer(cfg *Config, listenAddrs []net.Addr,
ZombieSweeperInterval: 1 * time.Minute,
ReservationTimeout: 10 * time.Minute,
MinChanSize: btcutil.Amount(cfg.MinChanSize),
MaxChanSize: btcutil.Amount(cfg.MaxChanSize),
MaxPendingChannels: cfg.MaxPendingChannels,
RejectPush: cfg.RejectPush,
NotifyOpenChannelEvent: s.channelNotifier.NotifyOpenChannelEvent,