mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-02-22 14:22:37 +01:00
Merge pull request #6956 from ellemouton/configureChanReserve
multi: configurable remote chan reserve
This commit is contained in:
commit
d2d3cf3408
9 changed files with 2212 additions and 2034 deletions
|
@ -233,6 +233,13 @@ var openChannelCommand = cli.Command{
|
||||||
Usage: "(optional) whether a scid-alias channel type" +
|
Usage: "(optional) whether a scid-alias channel type" +
|
||||||
" should be negotiated.",
|
" should be negotiated.",
|
||||||
},
|
},
|
||||||
|
cli.Uint64Flag{
|
||||||
|
Name: "remote_reserve_sats",
|
||||||
|
Usage: "(optional) the minimum number of satoshis we " +
|
||||||
|
"require the remote node to keep as a direct " +
|
||||||
|
"payment. If not specified, a default of 1% " +
|
||||||
|
"of the channel capacity will be used.",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Action: actionDecorator(openChannel),
|
Action: actionDecorator(openChannel),
|
||||||
}
|
}
|
||||||
|
@ -274,6 +281,7 @@ func openChannel(ctx *cli.Context) error {
|
||||||
MaxLocalCsv: uint32(ctx.Uint64("max_local_csv")),
|
MaxLocalCsv: uint32(ctx.Uint64("max_local_csv")),
|
||||||
ZeroConf: ctx.Bool("zero_conf"),
|
ZeroConf: ctx.Bool("zero_conf"),
|
||||||
ScidAlias: ctx.Bool("scid_alias"),
|
ScidAlias: ctx.Bool("scid_alias"),
|
||||||
|
RemoteChanReserveSat: ctx.Uint64("remote_reserve_sats"),
|
||||||
}
|
}
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
|
|
|
@ -7,9 +7,9 @@
|
||||||
|
|
||||||
## RPC
|
## RPC
|
||||||
|
|
||||||
The `RegisterConfirmationsNtfn` call of the `chainnotifier` RPC sub-server [now
|
* The `RegisterConfirmationsNtfn` call of the `chainnotifier` RPC sub-server
|
||||||
optionally supports returning the entire block that confirmed the
|
[now optionally supports returning the entire block that confirmed the
|
||||||
transaction](https://github.com/lightningnetwork/lnd/pull/6730).
|
transaction](https://github.com/lightningnetwork/lnd/pull/6730).
|
||||||
|
|
||||||
* [Add `macaroon_root_key` field to
|
* [Add `macaroon_root_key` field to
|
||||||
`InitWalletRequest`](https://github.com/lightningnetwork/lnd/pull/6457) to
|
`InitWalletRequest`](https://github.com/lightningnetwork/lnd/pull/6457) to
|
||||||
|
@ -30,6 +30,9 @@ transaction](https://github.com/lightningnetwork/lnd/pull/6730).
|
||||||
* [Catch and throw an error](https://github.com/lightningnetwork/lnd/pull/6945)
|
* [Catch and throw an error](https://github.com/lightningnetwork/lnd/pull/6945)
|
||||||
during `openchannel` if the local funding amount given is zero.
|
during `openchannel` if the local funding amount given is zero.
|
||||||
|
|
||||||
|
* [Make remote channel reserve amount configurable for
|
||||||
|
`openchannel`](https://github.com/lightningnetwork/lnd/pull/6956)
|
||||||
|
|
||||||
## Wallet
|
## Wallet
|
||||||
|
|
||||||
* [Allows Taproot public keys and tap scripts to be imported as watch-only
|
* [Allows Taproot public keys and tap scripts to be imported as watch-only
|
||||||
|
@ -153,3 +156,4 @@ crash](https://github.com/lightningnetwork/lnd/pull/7019).
|
||||||
* Olaoluwa Osuntokun
|
* Olaoluwa Osuntokun
|
||||||
* Oliver Gugger
|
* Oliver Gugger
|
||||||
* Priyansh Rastogi
|
* Priyansh Rastogi
|
||||||
|
* Roei Erez
|
||||||
|
|
|
@ -142,10 +142,11 @@ type reservationWithCtx struct {
|
||||||
forwardingPolicy htlcswitch.ForwardingPolicy
|
forwardingPolicy htlcswitch.ForwardingPolicy
|
||||||
|
|
||||||
// Constraints we require for the remote.
|
// Constraints we require for the remote.
|
||||||
remoteCsvDelay uint16
|
remoteCsvDelay uint16
|
||||||
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
|
||||||
|
@ -200,13 +201,14 @@ type InitFundingMsg struct {
|
||||||
// LocalFundingAmt is the size of the channel.
|
// LocalFundingAmt is the size of the channel.
|
||||||
LocalFundingAmt btcutil.Amount
|
LocalFundingAmt btcutil.Amount
|
||||||
|
|
||||||
// BaseFee is the base fee charged for routing payments regardless of the
|
// BaseFee is the base fee charged for routing payments regardless of
|
||||||
// number of milli-satoshis sent.
|
// the number of milli-satoshis sent.
|
||||||
BaseFee *uint64
|
BaseFee *uint64
|
||||||
|
|
||||||
// FeeRate is the fee rate in ppm (parts per million) that will be charged
|
// FeeRate is the fee rate in ppm (parts per million) that will be
|
||||||
// proportionally based on the value of each forwarded HTLC, the lowest
|
// charged proportionally based on the value of each forwarded HTLC, the
|
||||||
// possible rate is 0 with a granularity of 0.000001 (millionths).
|
// lowest possible rate is 0 with a granularity of 0.000001
|
||||||
|
// (millionths).
|
||||||
FeeRate *uint64
|
FeeRate *uint64
|
||||||
|
|
||||||
// PushAmt is the amount pushed to the counterparty.
|
// PushAmt is the amount pushed to the counterparty.
|
||||||
|
@ -224,6 +226,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
|
||||||
|
@ -261,8 +267,8 @@ type InitFundingMsg struct {
|
||||||
// support explicit channel type negotiation.
|
// support explicit channel type negotiation.
|
||||||
ChannelType *lnwire.ChannelType
|
ChannelType *lnwire.ChannelType
|
||||||
|
|
||||||
// Updates is a channel which updates to the opening status of the channel
|
// Updates is a channel which updates to the opening status of the
|
||||||
// are sent on.
|
// channel are sent on.
|
||||||
Updates chan *lnrpc.OpenStatusUpdate
|
Updates chan *lnrpc.OpenStatusUpdate
|
||||||
|
|
||||||
// Err is a channel which errors encountered during the funding flow are
|
// Err is a channel which errors encountered during the funding flow are
|
||||||
|
@ -1620,17 +1626,18 @@ func (f *Manager) handleFundingOpen(peer lnpeer.Peer,
|
||||||
f.activeReservations[peerIDKey] = make(pendingChannels)
|
f.activeReservations[peerIDKey] = make(pendingChannels)
|
||||||
}
|
}
|
||||||
resCtx := &reservationWithCtx{
|
resCtx := &reservationWithCtx{
|
||||||
reservation: reservation,
|
reservation: reservation,
|
||||||
chanAmt: amt,
|
chanAmt: amt,
|
||||||
forwardingPolicy: forwardingPolicy,
|
forwardingPolicy: forwardingPolicy,
|
||||||
remoteCsvDelay: remoteCsvDelay,
|
remoteCsvDelay: remoteCsvDelay,
|
||||||
remoteMinHtlc: minHtlc,
|
remoteMinHtlc: minHtlc,
|
||||||
remoteMaxValue: remoteMaxValue,
|
remoteMaxValue: remoteMaxValue,
|
||||||
remoteMaxHtlcs: maxHtlcs,
|
remoteMaxHtlcs: maxHtlcs,
|
||||||
maxLocalCsv: f.cfg.MaxLocalCSVDelay,
|
remoteChanReserve: chanReserve,
|
||||||
channelType: msg.ChannelType,
|
maxLocalCsv: f.cfg.MaxLocalCSVDelay,
|
||||||
err: make(chan error, 1),
|
channelType: msg.ChannelType,
|
||||||
peer: peer,
|
err: make(chan error, 1),
|
||||||
|
peer: peer,
|
||||||
}
|
}
|
||||||
f.activeReservations[peerIDKey][msg.PendingChannelID] = resCtx
|
f.activeReservations[peerIDKey][msg.PendingChannelID] = resCtx
|
||||||
f.resMtx.Unlock()
|
f.resMtx.Unlock()
|
||||||
|
@ -1850,14 +1857,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 +1867,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,
|
||||||
|
@ -3867,6 +3866,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
|
||||||
|
@ -4071,35 +4071,6 @@ func (f *Manager) handleInitFundingMsg(msg *InitFundingMsg) {
|
||||||
forwardingPolicy.FeeRate = lnwire.MilliSatoshi(*feeRate)
|
forwardingPolicy.FeeRate = lnwire.MilliSatoshi(*feeRate)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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.
|
|
||||||
peerIDKey := newSerializedKey(peerKey)
|
|
||||||
f.resMtx.Lock()
|
|
||||||
if _, ok := f.activeReservations[peerIDKey]; !ok {
|
|
||||||
f.activeReservations[peerIDKey] = make(pendingChannels)
|
|
||||||
}
|
|
||||||
|
|
||||||
resCtx := &reservationWithCtx{
|
|
||||||
chanAmt: capacity,
|
|
||||||
forwardingPolicy: forwardingPolicy,
|
|
||||||
remoteCsvDelay: remoteCsvDelay,
|
|
||||||
remoteMinHtlc: minHtlcIn,
|
|
||||||
remoteMaxValue: maxValue,
|
|
||||||
remoteMaxHtlcs: maxHtlcs,
|
|
||||||
maxLocalCsv: maxCSV,
|
|
||||||
channelType: msg.ChannelType,
|
|
||||||
reservation: reservation,
|
|
||||||
peer: msg.Peer,
|
|
||||||
updates: msg.Updates,
|
|
||||||
err: msg.Err,
|
|
||||||
}
|
|
||||||
f.activeReservations[peerIDKey][chanID] = resCtx
|
|
||||||
f.resMtx.Unlock()
|
|
||||||
|
|
||||||
// Update the timestamp once the InitFundingMsg has been handled.
|
|
||||||
defer resCtx.updateTimestamp()
|
|
||||||
|
|
||||||
// Once the reservation has been created, and indexed, queue a funding
|
// Once the reservation has been created, and indexed, queue a funding
|
||||||
// request to the remote peer, kicking off the funding workflow.
|
// request to the remote peer, kicking off the funding workflow.
|
||||||
ourContribution := reservation.OurContribution()
|
ourContribution := reservation.OurContribution()
|
||||||
|
@ -4110,10 +4081,66 @@ func (f *Manager) handleInitFundingMsg(msg *InitFundingMsg) {
|
||||||
|
|
||||||
log.Infof("Dust limit for pendingID(%x): %v", chanID, ourDustLimit)
|
log.Infof("Dust limit for pendingID(%x): %v", chanID, ourDustLimit)
|
||||||
|
|
||||||
// Finally, we'll use the current value of the channels and our default
|
// If the channel reserve is not specified, then we calculate an
|
||||||
// policy to determine of required commitment constraints for the
|
// appropriate amount here.
|
||||||
// remote party.
|
if chanReserve == 0 {
|
||||||
chanReserve := f.cfg.RequiredRemoteChanReserve(capacity, ourDustLimit)
|
chanReserve = f.cfg.RequiredRemoteChanReserve(
|
||||||
|
capacity, ourDustLimit,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
peerIDKey := newSerializedKey(peerKey)
|
||||||
|
f.resMtx.Lock()
|
||||||
|
if _, ok := f.activeReservations[peerIDKey]; !ok {
|
||||||
|
f.activeReservations[peerIDKey] = make(pendingChannels)
|
||||||
|
}
|
||||||
|
|
||||||
|
resCtx := &reservationWithCtx{
|
||||||
|
chanAmt: capacity,
|
||||||
|
forwardingPolicy: forwardingPolicy,
|
||||||
|
remoteCsvDelay: remoteCsvDelay,
|
||||||
|
remoteMinHtlc: minHtlcIn,
|
||||||
|
remoteMaxValue: maxValue,
|
||||||
|
remoteMaxHtlcs: maxHtlcs,
|
||||||
|
remoteChanReserve: chanReserve,
|
||||||
|
maxLocalCsv: maxCSV,
|
||||||
|
channelType: msg.ChannelType,
|
||||||
|
reservation: reservation,
|
||||||
|
peer: msg.Peer,
|
||||||
|
updates: msg.Updates,
|
||||||
|
err: msg.Err,
|
||||||
|
}
|
||||||
|
f.activeReservations[peerIDKey][chanID] = resCtx
|
||||||
|
f.resMtx.Unlock()
|
||||||
|
|
||||||
|
// Update the timestamp once the InitFundingMsg has been handled.
|
||||||
|
defer resCtx.updateTimestamp()
|
||||||
|
|
||||||
|
// Check the sanity of the selected channel constraints.
|
||||||
|
channelConstraints := &channeldb.ChannelConstraints{
|
||||||
|
DustLimit: ourDustLimit,
|
||||||
|
ChanReserve: chanReserve,
|
||||||
|
MaxPendingAmount: maxValue,
|
||||||
|
MinHTLC: minHtlcIn,
|
||||||
|
MaxAcceptedHtlcs: maxHtlcs,
|
||||||
|
CsvDelay: remoteCsvDelay,
|
||||||
|
}
|
||||||
|
err = lnwallet.VerifyConstraints(
|
||||||
|
channelConstraints, resCtx.maxLocalCsv, capacity,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
_, reserveErr := f.cancelReservationCtx(peerKey, chanID, false)
|
||||||
|
if reserveErr != nil {
|
||||||
|
log.Errorf("unable to cancel reservation: %v",
|
||||||
|
reserveErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
msg.Err <- err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// When opening a script enforced channel lease, include the required
|
// When opening a script enforced channel lease, include the required
|
||||||
// expiry TLV record in our proposal.
|
// expiry TLV record in our proposal.
|
||||||
|
|
|
@ -2795,6 +2795,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
|
||||||
|
@ -2815,19 +2816,20 @@ func TestFundingManagerCustomChannelParameters(t *testing.T) {
|
||||||
// workflow.
|
// workflow.
|
||||||
errChan := make(chan error, 1)
|
errChan := make(chan error, 1)
|
||||||
initReq := &InitFundingMsg{
|
initReq := &InitFundingMsg{
|
||||||
Peer: bob,
|
Peer: bob,
|
||||||
TargetPubkey: bob.privKey.PubKey(),
|
TargetPubkey: bob.privKey.PubKey(),
|
||||||
ChainHash: *fundingNetParams.GenesisHash,
|
ChainHash: *fundingNetParams.GenesisHash,
|
||||||
LocalFundingAmt: localAmt,
|
LocalFundingAmt: localAmt,
|
||||||
PushAmt: lnwire.NewMSatFromSatoshis(pushAmt),
|
PushAmt: lnwire.NewMSatFromSatoshis(pushAmt),
|
||||||
Private: false,
|
Private: false,
|
||||||
MaxValueInFlight: maxValueInFlight,
|
MaxValueInFlight: maxValueInFlight,
|
||||||
MinHtlcIn: minHtlcIn,
|
MinHtlcIn: minHtlcIn,
|
||||||
RemoteCsvDelay: csvDelay,
|
RemoteCsvDelay: csvDelay,
|
||||||
Updates: updateChan,
|
RemoteChanReserve: chanReserve,
|
||||||
Err: errChan,
|
Updates: updateChan,
|
||||||
BaseFee: &baseFee,
|
Err: errChan,
|
||||||
FeeRate: &feeRate,
|
BaseFee: &baseFee,
|
||||||
|
FeeRate: &feeRate,
|
||||||
}
|
}
|
||||||
|
|
||||||
alice.fundingMgr.InitFundingWorkflow(initReq)
|
alice.fundingMgr.InitFundingWorkflow(initReq)
|
||||||
|
@ -2872,6 +2874,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.
|
||||||
|
@ -3163,6 +3171,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) {
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -2217,6 +2217,13 @@ message OpenChannelRequest {
|
||||||
to use the default fee rate value specified in the config or 0.
|
to use the default fee rate value specified in the config or 0.
|
||||||
*/
|
*/
|
||||||
bool use_fee_rate = 24;
|
bool use_fee_rate = 24;
|
||||||
|
|
||||||
|
/*
|
||||||
|
The number of satoshis we require the remote peer to reserve. This value,
|
||||||
|
if specified, must be above the dust limit and below 20% of the channel
|
||||||
|
capacity.
|
||||||
|
*/
|
||||||
|
uint64 remote_chan_reserve_sat = 25;
|
||||||
}
|
}
|
||||||
message OpenStatusUpdate {
|
message OpenStatusUpdate {
|
||||||
oneof update {
|
oneof update {
|
||||||
|
|
|
@ -5679,6 +5679,11 @@
|
||||||
"use_fee_rate": {
|
"use_fee_rate": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "If use_fee_rate is true the open channel announcement will update the\nchannel fee rate with the value specified in fee_rate. In the case of\na fee_rate of 0 use_fee_rate is needed downstream to distinguish whether\nto use the default fee rate value specified in the config or 0."
|
"description": "If use_fee_rate is true the open channel announcement will update the\nchannel fee rate with the value specified in fee_rate. In the case of\na fee_rate of 0 use_fee_rate is needed downstream to distinguish whether\nto use the default fee rate value specified in the config or 0."
|
||||||
|
},
|
||||||
|
"remote_chan_reserve_sat": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "uint64",
|
||||||
|
"description": "The number of satoshis we require the remote peer to reserve. This value,\nif specified, must be above the dust limit and below 20% of the channel\ncapacity."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -466,60 +466,10 @@ func (r *ChannelReservation) CommitConstraints(c *channeldb.ChannelConstraints,
|
||||||
r.Lock()
|
r.Lock()
|
||||||
defer r.Unlock()
|
defer r.Unlock()
|
||||||
|
|
||||||
// Fail if the csv delay for our funds exceeds our maximum.
|
// First, verify the sanity of the channel constraints.
|
||||||
if c.CsvDelay > maxLocalCSVDelay {
|
err := VerifyConstraints(c, maxLocalCSVDelay, r.partialState.Capacity)
|
||||||
return ErrCsvDelayTooLarge(c.CsvDelay, maxLocalCSVDelay)
|
if err != nil {
|
||||||
}
|
return err
|
||||||
|
|
||||||
// The channel reserve should always be greater or equal to the dust
|
|
||||||
// limit. The reservation request should be denied if otherwise.
|
|
||||||
if c.DustLimit > c.ChanReserve {
|
|
||||||
return ErrChanReserveTooSmall(c.ChanReserve, c.DustLimit)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate against the maximum-sized witness script dust limit, and
|
|
||||||
// also ensure that the DustLimit is not too large.
|
|
||||||
maxWitnessLimit := DustLimitForSize(input.UnknownWitnessSize)
|
|
||||||
if c.DustLimit < maxWitnessLimit || c.DustLimit > 3*maxWitnessLimit {
|
|
||||||
return ErrInvalidDustLimit(c.DustLimit)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fail if we consider the channel reserve to be too large. We
|
|
||||||
// currently fail if it is greater than 20% of the channel capacity.
|
|
||||||
maxChanReserve := r.partialState.Capacity / 5
|
|
||||||
if c.ChanReserve > maxChanReserve {
|
|
||||||
return ErrChanReserveTooLarge(c.ChanReserve, maxChanReserve)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fail if the minimum HTLC value is too large. If this is too large,
|
|
||||||
// the channel won't be useful for sending small payments. This limit
|
|
||||||
// is currently set to maxValueInFlight, effectively letting the remote
|
|
||||||
// setting this as large as it wants.
|
|
||||||
if c.MinHTLC > c.MaxPendingAmount {
|
|
||||||
return ErrMinHtlcTooLarge(c.MinHTLC, c.MaxPendingAmount)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fail if maxHtlcs is above the maximum allowed number of 483. This
|
|
||||||
// number is specified in BOLT-02.
|
|
||||||
if c.MaxAcceptedHtlcs > uint16(input.MaxHTLCNumber/2) {
|
|
||||||
return ErrMaxHtlcNumTooLarge(
|
|
||||||
c.MaxAcceptedHtlcs, uint16(input.MaxHTLCNumber/2),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fail if we consider maxHtlcs too small. If this is too small we
|
|
||||||
// cannot offer many HTLCs to the remote.
|
|
||||||
const minNumHtlc = 5
|
|
||||||
if c.MaxAcceptedHtlcs < minNumHtlc {
|
|
||||||
return ErrMaxHtlcNumTooSmall(c.MaxAcceptedHtlcs, minNumHtlc)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fail if we consider maxValueInFlight too small. We currently require
|
|
||||||
// the remote to at least allow minNumHtlc * minHtlc in flight.
|
|
||||||
if c.MaxPendingAmount < minNumHtlc*c.MinHTLC {
|
|
||||||
return ErrMaxValueInFlightTooSmall(
|
|
||||||
c.MaxPendingAmount, minNumHtlc*c.MinHTLC,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Our dust limit should always be less than or equal to our proposed
|
// Our dust limit should always be less than or equal to our proposed
|
||||||
|
@ -812,6 +762,70 @@ func (r *ChannelReservation) Cancel() error {
|
||||||
return <-errChan
|
return <-errChan
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// VerifyConstraints is a helper function that can be used to check the sanity
|
||||||
|
// of various channel constraints.
|
||||||
|
func VerifyConstraints(c *channeldb.ChannelConstraints,
|
||||||
|
maxLocalCSVDelay uint16, channelCapacity btcutil.Amount) error {
|
||||||
|
|
||||||
|
// Fail if the csv delay for our funds exceeds our maximum.
|
||||||
|
if c.CsvDelay > maxLocalCSVDelay {
|
||||||
|
return ErrCsvDelayTooLarge(c.CsvDelay, maxLocalCSVDelay)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The channel reserve should always be greater or equal to the dust
|
||||||
|
// limit. The reservation request should be denied if otherwise.
|
||||||
|
if c.DustLimit > c.ChanReserve {
|
||||||
|
return ErrChanReserveTooSmall(c.ChanReserve, c.DustLimit)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate against the maximum-sized witness script dust limit, and
|
||||||
|
// also ensure that the DustLimit is not too large.
|
||||||
|
maxWitnessLimit := DustLimitForSize(input.UnknownWitnessSize)
|
||||||
|
if c.DustLimit < maxWitnessLimit || c.DustLimit > 3*maxWitnessLimit {
|
||||||
|
return ErrInvalidDustLimit(c.DustLimit)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fail if we consider the channel reserve to be too large. We
|
||||||
|
// currently fail if it is greater than 20% of the channel capacity.
|
||||||
|
maxChanReserve := channelCapacity / 5
|
||||||
|
if c.ChanReserve > maxChanReserve {
|
||||||
|
return ErrChanReserveTooLarge(c.ChanReserve, maxChanReserve)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fail if the minimum HTLC value is too large. If this is too large,
|
||||||
|
// the channel won't be useful for sending small payments. This limit
|
||||||
|
// is currently set to maxValueInFlight, effectively letting the remote
|
||||||
|
// setting this as large as it wants.
|
||||||
|
if c.MinHTLC > c.MaxPendingAmount {
|
||||||
|
return ErrMinHtlcTooLarge(c.MinHTLC, c.MaxPendingAmount)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fail if maxHtlcs is above the maximum allowed number of 483. This
|
||||||
|
// number is specified in BOLT-02.
|
||||||
|
if c.MaxAcceptedHtlcs > uint16(input.MaxHTLCNumber/2) {
|
||||||
|
return ErrMaxHtlcNumTooLarge(
|
||||||
|
c.MaxAcceptedHtlcs, uint16(input.MaxHTLCNumber/2),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fail if we consider maxHtlcs too small. If this is too small we
|
||||||
|
// cannot offer many HTLCs to the remote.
|
||||||
|
const minNumHtlc = 5
|
||||||
|
if c.MaxAcceptedHtlcs < minNumHtlc {
|
||||||
|
return ErrMaxHtlcNumTooSmall(c.MaxAcceptedHtlcs, minNumHtlc)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fail if we consider maxValueInFlight too small. We currently require
|
||||||
|
// the remote to at least allow minNumHtlc * minHtlc in flight.
|
||||||
|
if c.MaxPendingAmount < minNumHtlc*c.MinHTLC {
|
||||||
|
return ErrMaxValueInFlightTooSmall(
|
||||||
|
c.MaxPendingAmount, minNumHtlc*c.MinHTLC,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// OpenChannelDetails wraps the finalized fully confirmed channel which
|
// OpenChannelDetails wraps the finalized fully confirmed channel which
|
||||||
// resulted from a ChannelReservation instance with details concerning exactly
|
// resulted from a ChannelReservation instance with details concerning exactly
|
||||||
// _where_ in the chain the channel was ultimately opened.
|
// _where_ in the chain the channel was ultimately opened.
|
||||||
|
|
41
rpcserver.go
41
rpcserver.go
|
@ -1910,6 +1910,7 @@ func (r *rpcServer) parseOpenChannelReq(in *lnrpc.OpenChannelRequest,
|
||||||
remoteCsvDelay := uint16(in.RemoteCsvDelay)
|
remoteCsvDelay := uint16(in.RemoteCsvDelay)
|
||||||
maxValue := lnwire.MilliSatoshi(in.RemoteMaxValueInFlightMsat)
|
maxValue := lnwire.MilliSatoshi(in.RemoteMaxValueInFlightMsat)
|
||||||
maxHtlcs := uint16(in.RemoteMaxHtlcs)
|
maxHtlcs := uint16(in.RemoteMaxHtlcs)
|
||||||
|
remoteChanReserve := btcutil.Amount(in.RemoteChanReserveSat)
|
||||||
|
|
||||||
globalFeatureSet := r.server.featureMgr.Get(feature.SetNodeAnn)
|
globalFeatureSet := r.server.featureMgr.Get(feature.SetNodeAnn)
|
||||||
|
|
||||||
|
@ -1937,6 +1938,13 @@ func (r *rpcServer) parseOpenChannelReq(in *lnrpc.OpenChannelRequest,
|
||||||
channelFeeRate = &in.FeeRate
|
channelFeeRate = &in.FeeRate
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ensure that the remote channel reserve does not exceed 20% of the
|
||||||
|
// channel capacity.
|
||||||
|
if remoteChanReserve >= localFundingAmt/5 {
|
||||||
|
return nil, fmt.Errorf("remote channel reserve must be less " +
|
||||||
|
"than the %%20 of the channel capacity")
|
||||||
|
}
|
||||||
|
|
||||||
// Ensure that the user doesn't exceed the current soft-limit for
|
// Ensure that the user doesn't exceed the current soft-limit for
|
||||||
// channel size. If the funding amount is above the soft-limit, then
|
// channel size. If the funding amount is above the soft-limit, then
|
||||||
// we'll reject the request.
|
// we'll reject the request.
|
||||||
|
@ -2092,22 +2100,23 @@ func (r *rpcServer) parseOpenChannelReq(in *lnrpc.OpenChannelRequest,
|
||||||
// open a new channel. A stream is returned in place, this stream will
|
// open a new channel. A stream is returned in place, this stream will
|
||||||
// be used to consume updates of the state of the pending channel.
|
// be used to consume updates of the state of the pending channel.
|
||||||
return &funding.InitFundingMsg{
|
return &funding.InitFundingMsg{
|
||||||
TargetPubkey: nodePubKey,
|
TargetPubkey: nodePubKey,
|
||||||
ChainHash: *r.cfg.ActiveNetParams.GenesisHash,
|
ChainHash: *r.cfg.ActiveNetParams.GenesisHash,
|
||||||
LocalFundingAmt: localFundingAmt,
|
LocalFundingAmt: localFundingAmt,
|
||||||
BaseFee: channelBaseFee,
|
BaseFee: channelBaseFee,
|
||||||
FeeRate: channelFeeRate,
|
FeeRate: channelFeeRate,
|
||||||
PushAmt: lnwire.NewMSatFromSatoshis(remoteInitialBalance),
|
PushAmt: lnwire.NewMSatFromSatoshis(remoteInitialBalance),
|
||||||
MinHtlcIn: minHtlcIn,
|
MinHtlcIn: minHtlcIn,
|
||||||
FundingFeePerKw: feeRate,
|
FundingFeePerKw: feeRate,
|
||||||
Private: in.Private,
|
Private: in.Private,
|
||||||
RemoteCsvDelay: remoteCsvDelay,
|
RemoteCsvDelay: remoteCsvDelay,
|
||||||
MinConfs: minConfs,
|
RemoteChanReserve: remoteChanReserve,
|
||||||
ShutdownScript: script,
|
MinConfs: minConfs,
|
||||||
MaxValueInFlight: maxValue,
|
ShutdownScript: script,
|
||||||
MaxHtlcs: maxHtlcs,
|
MaxValueInFlight: maxValue,
|
||||||
MaxLocalCsv: uint16(in.MaxLocalCsv),
|
MaxHtlcs: maxHtlcs,
|
||||||
ChannelType: channelType,
|
MaxLocalCsv: uint16(in.MaxLocalCsv),
|
||||||
|
ChannelType: channelType,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue