Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions cmd/lncli/cmd_open_channel.go
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,11 @@ var openChannelCommand = cli.Command{
Usage: "(optional) the maximum value in msat that " +
"can be pending within the channel at any given time",
},
cli.Int64Flag{
Name: "remote_chan_reserve_sat",
Usage: "(optional) the minimum number of satoshis we " +
"require the remote node to keep as a direct payment",
},
},
Action: actionDecorator(openChannel),
}
Expand Down Expand Up @@ -239,6 +244,7 @@ func openChannel(ctx *cli.Context) error {
CloseAddress: ctx.String("close_address"),
RemoteMaxValueInFlightMsat: ctx.Uint64("remote_max_value_in_flight_msat"),
MaxLocalCsv: uint32(ctx.Uint64("max_local_csv")),
RemoteChanReserveSat: ctx.Int64("remote_chan_reserve_sat"),
}

switch {
Expand Down
80 changes: 46 additions & 34 deletions funding/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,10 +139,11 @@ type reservationWithCtx struct {
chanAmt btcutil.Amount

// Constraints we require for the remote.
remoteCsvDelay uint16
remoteMinHtlc lnwire.MilliSatoshi
remoteMaxValue lnwire.MilliSatoshi
remoteMaxHtlcs uint16
remoteCsvDelay uint16
remoteMinHtlc lnwire.MilliSatoshi
remoteMaxValue lnwire.MilliSatoshi
remoteMaxHtlcs uint16
remoteChanReserve btcutil.Amount

// maxLocalCsv is the maximum csv we will accept from the remote.
maxLocalCsv uint16
Expand Down Expand Up @@ -208,6 +209,9 @@ type InitFundingMsg struct {
// RemoteCsvDelay is the CSV delay we require for the remote peer.
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
// output selected to fund the channel should satisfy.
MinConfs int32
Expand Down Expand Up @@ -1433,15 +1437,16 @@ func (f *Manager) handleFundingOpen(peer lnpeer.Peer,
f.activeReservations[peerIDKey] = make(pendingChannels)
}
resCtx := &reservationWithCtx{
reservation: reservation,
chanAmt: amt,
remoteCsvDelay: remoteCsvDelay,
remoteMinHtlc: minHtlc,
remoteMaxValue: remoteMaxValue,
remoteMaxHtlcs: maxHtlcs,
maxLocalCsv: f.cfg.MaxLocalCSVDelay,
err: make(chan error, 1),
peer: peer,
reservation: reservation,
chanAmt: amt,
remoteCsvDelay: remoteCsvDelay,
remoteMinHtlc: minHtlc,
remoteMaxValue: remoteMaxValue,
remoteMaxHtlcs: maxHtlcs,
remoteChanReserve: chanReserve,
maxLocalCsv: f.cfg.MaxLocalCSVDelay,
err: make(chan error, 1),
peer: peer,
}
f.activeReservations[peerIDKey][msg.PendingChannelID] = resCtx
f.resMtx.Unlock()
Expand Down Expand Up @@ -1576,11 +1581,6 @@ func (f *Manager) handleFundingAccept(peer lnpeer.Peer,
return
}

// As they've accepted our channel constraints, we'll regenerate them
// here so we can properly commit their accepted constraints to the
// reservation.
chanReserve := f.cfg.RequiredRemoteChanReserve(resCtx.chanAmt, msg.DustLimit)

// The remote node has responded with their portion of the channel
// contribution. At this point, we can process their contribution which
// allows us to construct and sign both the commitment transaction, and
Expand All @@ -1591,7 +1591,7 @@ func (f *Manager) handleFundingAccept(peer lnpeer.Peer,
ChannelConstraints: channeldb.ChannelConstraints{
DustLimit: msg.DustLimit,
MaxPendingAmount: resCtx.remoteMaxValue,
ChanReserve: chanReserve,
ChanReserve: resCtx.remoteChanReserve,
MinHTLC: resCtx.remoteMinHtlc,
MaxAcceptedHtlcs: resCtx.remoteMaxHtlcs,
CsvDelay: resCtx.remoteCsvDelay,
Expand Down Expand Up @@ -3098,6 +3098,7 @@ func (f *Manager) handleInitFundingMsg(msg *InitFundingMsg) {
maxValue = msg.MaxValueInFlight
maxHtlcs = msg.MaxHtlcs
maxCSV = msg.MaxLocalCsv
chanReserve = msg.RemoteChanReserve
)

// If no maximum CSV delay was set for this channel, we use our default
Expand Down Expand Up @@ -3254,6 +3255,21 @@ func (f *Manager) handleInitFundingMsg(msg *InitFundingMsg) {
maxHtlcs = f.cfg.RequiredRemoteMaxHTLCs(capacity)
}

// if no channel reserve was specified, use the default one.
if chanReserve == 0 {
chanReserve = f.cfg.RequiredRemoteChanReserve(capacity, ourDustLimit)
}
if chanReserve < ourDustLimit {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We already have a similar check in CommitConstraints, though that doesn't check against the capacity.

msg.Err <- fmt.Errorf("remote channel reserve %v is less than the "+
"dust limit", chanReserve)
return
}
if chanReserve >= capacity {
msg.Err <- fmt.Errorf("remote channel reserve %v should be less "+
"than the channel capacity", chanReserve)
return
}

// If a pending channel map for this peer isn't already created, then
// we create one, ultimately allowing us to track this pending
// reservation within the target peer.
Expand All @@ -3264,16 +3280,17 @@ func (f *Manager) handleInitFundingMsg(msg *InitFundingMsg) {
}

resCtx := &reservationWithCtx{
chanAmt: capacity,
remoteCsvDelay: remoteCsvDelay,
remoteMinHtlc: minHtlcIn,
remoteMaxValue: maxValue,
remoteMaxHtlcs: maxHtlcs,
maxLocalCsv: maxCSV,
reservation: reservation,
peer: msg.Peer,
updates: msg.Updates,
err: msg.Err,
chanAmt: capacity,
remoteCsvDelay: remoteCsvDelay,
remoteMinHtlc: minHtlcIn,
remoteMaxValue: maxValue,
remoteMaxHtlcs: maxHtlcs,
remoteChanReserve: chanReserve,
maxLocalCsv: maxCSV,
reservation: reservation,
peer: msg.Peer,
updates: msg.Updates,
err: msg.Err,
}
f.activeReservations[peerIDKey][chanID] = resCtx
f.resMtx.Unlock()
Expand All @@ -3285,11 +3302,6 @@ func (f *Manager) handleInitFundingMsg(msg *InitFundingMsg) {
// request to the remote peer, kicking off the funding workflow.
ourContribution := reservation.OurContribution()

// 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)

log.Infof("Starting funding workflow with %v for pending_id(%x), "+
"committype=%v", msg.Peer.Address(), chanID, commitType)

Expand Down
74 changes: 63 additions & 11 deletions funding/manager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2580,6 +2580,7 @@ func TestFundingManagerCustomChannelParameters(t *testing.T) {
const minHtlcIn = 1234
const maxValueInFlight = 50000
const fundingAmt = 5000000
const chanReserve = 100000

// We will consume the channel updates as we go, so no buffering is
// needed.
Expand All @@ -2593,17 +2594,18 @@ func TestFundingManagerCustomChannelParameters(t *testing.T) {
// workflow.
errChan := make(chan error, 1)
initReq := &InitFundingMsg{
Peer: bob,
TargetPubkey: bob.privKey.PubKey(),
ChainHash: *fundingNetParams.GenesisHash,
LocalFundingAmt: localAmt,
PushAmt: lnwire.NewMSatFromSatoshis(pushAmt),
Private: false,
MaxValueInFlight: maxValueInFlight,
MinHtlcIn: minHtlcIn,
RemoteCsvDelay: csvDelay,
Updates: updateChan,
Err: errChan,
Peer: bob,
TargetPubkey: bob.privKey.PubKey(),
ChainHash: *fundingNetParams.GenesisHash,
LocalFundingAmt: localAmt,
PushAmt: lnwire.NewMSatFromSatoshis(pushAmt),
Private: false,
MaxValueInFlight: maxValueInFlight,
MinHtlcIn: minHtlcIn,
RemoteCsvDelay: csvDelay,
Updates: updateChan,
Err: errChan,
RemoteChanReserve: chanReserve,
}

alice.fundingMgr.InitFundingWorkflow(initReq)
Expand Down Expand Up @@ -2648,6 +2650,12 @@ func TestFundingManagerCustomChannelParameters(t *testing.T) {
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

// Let Bob handle the init message.
Expand Down Expand Up @@ -3095,6 +3103,50 @@ func TestFundingManagerRejectPush(t *testing.T) {
}
}

// TestFundingManagerInvalidChanReserve ensures proper validation is done on
// remoteChanReserve parameter sent to open channel.
func TestFundingManagerInvalidChanReserve(t *testing.T) {
t.Parallel()

testInvalidChanReserve := func(chanReserve btcutil.Amount) {
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: *fundingNetParams.GenesisHash,
LocalFundingAmt: 500000,
PushAmt: lnwire.NewMSatFromSatoshis(10),
Private: false,
Updates: updateChan,
RemoteChanReserve: chanReserve,
Err: errChan,
}

alice.fundingMgr.InitFundingWorkflow(initReq)

// Alice should have sent the OpenChannel message to Bob.
select {
case <-alice.msgChan:
t.Fatalf("init funding workflow should fail with invalid chan"+
"reserve %v", chanReserve)
case <-initReq.Err:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should assert on the expected error here.

case <-time.After(time.Second * 5):
t.Fatalf("alice did not send OpenChannel message")
}
}

// Test channel reserve below dust
testInvalidChanReserve(300)

// test channel reserve above capacity
testInvalidChanReserve(500000)
}

// TestFundingManagerMaxConfs ensures that we don't accept a funding proposal
// that proposes a MinAcceptDepth greater than the maximum number of
// confirmations we're willing to accept.
Expand Down
Loading