funding: enable configurable channel reserve

In this commit, the channel reserve of an initiated channel is made to
be optionally configurable.
This commit is contained in:
Roei Erez authored and Elle Mouton committed 2022-10-07 14:57:05 +02:00
1 parent 10f0eddd51
commit 6467b0ee12
2 files changed
+117 -24

No files matched your search

+27 -24
View File
@@ -146,6 +146,7 @@ type reservationWithCtx struct {
remoteMinHtlc lnwire.MilliSatoshi remoteMinHtlc lnwire.MilliSatoshi
remoteMaxValue lnwire.MilliSatoshi remoteMaxValue lnwire.MilliSatoshi
remoteMaxHtlcs uint16 remoteMaxHtlcs uint16
remoteChanReserve btcutil.Amount
// maxLocalCsv is the maximum csv we will accept from the remote. // maxLocalCsv is the maximum csv we will accept from the remote.
maxLocalCsv uint16 maxLocalCsv uint16
@@ -224,6 +225,10 @@ type InitFundingMsg struct {
// RemoteCsvDelay is the CSV delay we require for the remote peer. // RemoteCsvDelay is the CSV delay we require for the remote peer.
RemoteCsvDelay uint16 RemoteCsvDelay uint16
// RemoteChanReserve is the channel reserve we required for the remote
// peer.
RemoteChanReserve btcutil.Amount
// MinConfs indicates the minimum number of confirmations that each // MinConfs indicates the minimum number of confirmations that each
// output selected to fund the channel should satisfy. // output selected to fund the channel should satisfy.
MinConfs int32 MinConfs int32
@@ -1627,6 +1632,7 @@ func (f *Manager) handleFundingOpen(peer lnpeer.Peer,
remoteMinHtlc: minHtlc, remoteMinHtlc: minHtlc,
remoteMaxValue: remoteMaxValue, remoteMaxValue: remoteMaxValue,
remoteMaxHtlcs: maxHtlcs, remoteMaxHtlcs: maxHtlcs,
remoteChanReserve: chanReserve,
maxLocalCsv: f.cfg.MaxLocalCSVDelay, maxLocalCsv: f.cfg.MaxLocalCSVDelay,
channelType: msg.ChannelType, channelType: msg.ChannelType,
err: make(chan error, 1), err: make(chan error, 1),
@@ -1850,14 +1856,6 @@ func (f *Manager) handleFundingAccept(peer lnpeer.Peer,
return return
} }
// As they've accepted our channel constraints, we'll regenerate them
// here so we can properly commit their accepted constraints to the
// reservation. Also make sure that we re-generate the ChannelReserve
// with our dust limit or we can get stuck channels.
chanReserve := f.cfg.RequiredRemoteChanReserve(
resCtx.chanAmt, resCtx.reservation.OurContribution().DustLimit,
)
// The remote node has responded with their portion of the channel // The remote node has responded with their portion of the channel
// contribution. At this point, we can process their contribution which // contribution. At this point, we can process their contribution which
// allows us to construct and sign both the commitment transaction, and // allows us to construct and sign both the commitment transaction, and
@@ -1868,7 +1866,7 @@ func (f *Manager) handleFundingAccept(peer lnpeer.Peer,
ChannelConstraints: channeldb.ChannelConstraints{ ChannelConstraints: channeldb.ChannelConstraints{
DustLimit: msg.DustLimit, DustLimit: msg.DustLimit,
MaxPendingAmount: resCtx.remoteMaxValue, MaxPendingAmount: resCtx.remoteMaxValue,
ChanReserve: chanReserve, ChanReserve: resCtx.remoteChanReserve,
MinHTLC: resCtx.remoteMinHtlc, MinHTLC: resCtx.remoteMinHtlc,
MaxAcceptedHtlcs: resCtx.remoteMaxHtlcs, MaxAcceptedHtlcs: resCtx.remoteMaxHtlcs,
CsvDelay: resCtx.remoteCsvDelay, CsvDelay: resCtx.remoteCsvDelay,
@@ -3888,6 +3886,7 @@ func (f *Manager) handleInitFundingMsg(msg *InitFundingMsg) {
maxValue = msg.MaxValueInFlight maxValue = msg.MaxValueInFlight
maxHtlcs = msg.MaxHtlcs maxHtlcs = msg.MaxHtlcs
maxCSV = msg.MaxLocalCsv maxCSV = msg.MaxLocalCsv
chanReserve = msg.RemoteChanReserve
) )
// If no maximum CSV delay was set for this channel, we use our default // If no maximum CSV delay was set for this channel, we use our default
@@ -4092,6 +4091,24 @@ func (f *Manager) handleInitFundingMsg(msg *InitFundingMsg) {
forwardingPolicy.FeeRate = lnwire.MilliSatoshi(*feeRate) forwardingPolicy.FeeRate = lnwire.MilliSatoshi(*feeRate)
} }
// Once the reservation has been created, and indexed, queue a funding
// request to the remote peer, kicking off the funding workflow.
ourContribution := reservation.OurContribution()
// Fetch our dust limit which is part of the default channel
// constraints, and log it.
ourDustLimit := ourContribution.DustLimit
log.Infof("Dust limit for pendingID(%x): %v", chanID, ourDustLimit)
// If the channel reserve is not specified, then we calculate an
// appropriate amount here.
if chanReserve == 0 {
chanReserve = f.cfg.RequiredRemoteChanReserve(
capacity, ourDustLimit,
)
}
// If a pending channel map for this peer isn't already created, then // If a pending channel map for this peer isn't already created, then
// we create one, ultimately allowing us to track this pending // we create one, ultimately allowing us to track this pending
// reservation within the target peer. // reservation within the target peer.
@@ -4108,6 +4125,7 @@ func (f *Manager) handleInitFundingMsg(msg *InitFundingMsg) {
remoteMinHtlc: minHtlcIn, remoteMinHtlc: minHtlcIn,
remoteMaxValue: maxValue, remoteMaxValue: maxValue,
remoteMaxHtlcs: maxHtlcs, remoteMaxHtlcs: maxHtlcs,
remoteChanReserve: chanReserve,
maxLocalCsv: maxCSV, maxLocalCsv: maxCSV,
channelType: msg.ChannelType, channelType: msg.ChannelType,
reservation: reservation, reservation: reservation,
@@ -4121,21 +4139,6 @@ func (f *Manager) handleInitFundingMsg(msg *InitFundingMsg) {
// Update the timestamp once the InitFundingMsg has been handled. // Update the timestamp once the InitFundingMsg has been handled.
defer resCtx.updateTimestamp() defer resCtx.updateTimestamp()
// Once the reservation has been created, and indexed, queue a funding
// request to the remote peer, kicking off the funding workflow.
ourContribution := reservation.OurContribution()
// Fetch our dust limit which is part of the default channel
// constraints, and log it.
ourDustLimit := ourContribution.DustLimit
log.Infof("Dust limit for pendingID(%x): %v", chanID, ourDustLimit)
// Finally, we'll use the current value of the channels and our default
// policy to determine of required commitment constraints for the
// remote party.
chanReserve := f.cfg.RequiredRemoteChanReserve(capacity, ourDustLimit)
// Check the sanity of the selected channel constraints. // Check the sanity of the selected channel constraints.
channelConstraints := &channeldb.ChannelConstraints{ channelConstraints := &channeldb.ChannelConstraints{
DustLimit: ourDustLimit, DustLimit: ourDustLimit,
+90
View File
@@ -2765,6 +2765,7 @@ func TestFundingManagerCustomChannelParameters(t *testing.T) {
const minHtlcIn = 1234 const minHtlcIn = 1234
const maxValueInFlight = 50000 const maxValueInFlight = 50000
const fundingAmt = 5000000 const fundingAmt = 5000000
const chanReserve = 100000
// Use custom channel fees. // Use custom channel fees.
// These will show up in the channel reservation context // These will show up in the channel reservation context
@@ -2794,6 +2795,7 @@ func TestFundingManagerCustomChannelParameters(t *testing.T) {
MaxValueInFlight: maxValueInFlight, MaxValueInFlight: maxValueInFlight,
MinHtlcIn: minHtlcIn, MinHtlcIn: minHtlcIn,
RemoteCsvDelay: csvDelay, RemoteCsvDelay: csvDelay,
RemoteChanReserve: chanReserve,
Updates: updateChan, Updates: updateChan,
Err: errChan, Err: errChan,
BaseFee: &baseFee, BaseFee: &baseFee,
@@ -2842,6 +2844,12 @@ func TestFundingManagerCustomChannelParameters(t *testing.T) {
maxValueInFlight, openChannelReq.MaxValueInFlight) maxValueInFlight, openChannelReq.MaxValueInFlight)
} }
// Check that the custom remoteChanReserve value is sent.
if openChannelReq.ChannelReserve != chanReserve {
t.Fatalf("expected OpenChannel to have chanReserve %v, got %v",
chanReserve, openChannelReq.ChannelReserve)
}
chanID := openChannelReq.PendingChannelID chanID := openChannelReq.PendingChannelID
// Let Bob handle the init message. // Let Bob handle the init message.
@@ -3133,6 +3141,88 @@ func TestFundingManagerCustomChannelParameters(t *testing.T) {
} }
} }
// TestFundingManagerInvalidChanReserve ensures proper validation is done on
// remoteChanReserve parameter sent to open channel.
func TestFundingManagerInvalidChanReserve(t *testing.T) {
t.Parallel()
var (
fundingAmt = btcutil.Amount(500000)
pushAmt = lnwire.NewMSatFromSatoshis(10)
genesisHash = *fundingNetParams.GenesisHash
)
tests := []struct {
name string
chanReserve btcutil.Amount
expectErr bool
errorContains string
}{
{
name: "Use default chan reserve",
chanReserve: 0,
},
{
name: "Above dust but below 1% of the capacity",
chanReserve: 400,
},
{
name: "Channel reserve below dust",
chanReserve: 300,
expectErr: true,
errorContains: "channel reserve of 300 sat is too " +
"small",
},
{
name: "Channel reserve more than 20% of the " +
"channel capacity",
chanReserve: fundingAmt,
expectErr: true,
errorContains: "channel reserve is too large",
},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
t.Parallel()
alice, bob := setupFundingManagers(t)
defer tearDownFundingManagers(t, alice, bob)
// Create a funding request and start the workflow.
updateChan := make(chan *lnrpc.OpenStatusUpdate)
errChan := make(chan error, 1)
initReq := &InitFundingMsg{
Peer: bob,
TargetPubkey: bob.privKey.PubKey(),
ChainHash: genesisHash,
LocalFundingAmt: fundingAmt,
PushAmt: pushAmt,
Updates: updateChan,
RemoteChanReserve: test.chanReserve,
Err: errChan,
}
alice.fundingMgr.InitFundingWorkflow(initReq)
var err error
select {
case <-alice.msgChan:
case err = <-initReq.Err:
case <-time.After(time.Second * 5):
t.Fatalf("no message or error received")
}
if !test.expectErr {
require.NoError(t, err)
return
}
require.ErrorContains(t, err, test.errorContains)
})
}
}
// TestFundingManagerMaxPendingChannels checks that trying to open another // TestFundingManagerMaxPendingChannels checks that trying to open another
// channel with the same peer when MaxPending channels are pending fails. // channel with the same peer when MaxPending channels are pending fails.
func TestFundingManagerMaxPendingChannels(t *testing.T) { func TestFundingManagerMaxPendingChannels(t *testing.T) {