2020-06-16 20:33:06 -04:00
|
|
|
package chancloser
|
2017-11-23 00:54:28 -06:00
|
|
|
|
|
|
|
import (
|
2019-12-03 11:38:29 +02:00
|
|
|
"bytes"
|
2017-11-23 00:54:28 -06:00
|
|
|
"fmt"
|
|
|
|
|
2023-01-19 19:43:47 -08:00
|
|
|
"github.com/btcsuite/btcd/btcec/v2/schnorr/musig2"
|
2022-02-23 14:48:00 +01:00
|
|
|
"github.com/btcsuite/btcd/btcutil"
|
2020-11-09 09:34:51 +02:00
|
|
|
"github.com/btcsuite/btcd/chaincfg"
|
|
|
|
"github.com/btcsuite/btcd/txscript"
|
2018-07-31 00:17:17 -07:00
|
|
|
"github.com/btcsuite/btcd/wire"
|
2017-11-23 00:54:28 -06:00
|
|
|
"github.com/davecgh/go-spew/spew"
|
lnwallet/chancloser: properly compute initial fee of cop close txn
In this commit, we modify the way we compute the starting ideal fee for
the co-op close transaction. Before thsi commit, channel.CalcFee was
used, which'll compute the fee based on the commitment transaction
itself, rathern than the co-op close transaction. As the co-op close
transaction is potentailly bigger (two P2TR outputs) than the commitment
transaction, this can cause us to under estimate the fee, which can
result in the fee rate being too low to propagate.
To remedy this, we now compute a fee estimate from scratch, based on the
delivery fees of the two parties.
We also add a bug fix in the chancloser unit tests that wasn't caught
due to loop variable shadowing.
The wallet import itest has been updated as well, since we'll now pay
600 extra saothis to close the channel, since we're accounting for the
added weight of the P2TR outputs.
Fixes #6953
2022-09-29 19:49:44 -07:00
|
|
|
"github.com/lightningnetwork/lnd/channeldb"
|
2017-11-23 00:54:28 -06:00
|
|
|
"github.com/lightningnetwork/lnd/htlcswitch"
|
2022-07-26 16:39:19 -07:00
|
|
|
"github.com/lightningnetwork/lnd/input"
|
2020-07-29 09:27:22 +02:00
|
|
|
"github.com/lightningnetwork/lnd/labels"
|
2017-11-23 00:54:28 -06:00
|
|
|
"github.com/lightningnetwork/lnd/lnwallet"
|
2019-10-30 19:43:05 -07:00
|
|
|
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
2017-11-23 00:54:28 -06:00
|
|
|
"github.com/lightningnetwork/lnd/lnwire"
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
2020-06-16 20:13:28 -04:00
|
|
|
// ErrChanAlreadyClosing is returned when a channel shutdown is attempted
|
|
|
|
// more than once.
|
2017-11-23 00:54:28 -06:00
|
|
|
ErrChanAlreadyClosing = fmt.Errorf("channel shutdown already initiated")
|
|
|
|
|
|
|
|
// ErrChanCloseNotFinished is returned when a caller attempts to access
|
2020-06-16 20:13:28 -04:00
|
|
|
// a field or function that is contingent on the channel closure negotiation
|
|
|
|
// already being completed.
|
2017-11-23 00:54:28 -06:00
|
|
|
ErrChanCloseNotFinished = fmt.Errorf("close negotiation not finished")
|
|
|
|
|
2020-06-16 20:13:28 -04:00
|
|
|
// ErrInvalidState is returned when the closing state machine receives a
|
|
|
|
// message while it is in an unknown state.
|
2017-11-23 00:54:28 -06:00
|
|
|
ErrInvalidState = fmt.Errorf("invalid state")
|
2019-12-03 11:38:29 +02:00
|
|
|
|
2020-06-11 15:25:05 -04:00
|
|
|
// ErrUpfrontShutdownScriptMismatch is returned when a peer or end user
|
2020-06-16 20:13:28 -04:00
|
|
|
// provides a cooperative close script which does not match the upfront
|
|
|
|
// shutdown script previously set for that party.
|
|
|
|
ErrUpfrontShutdownScriptMismatch = fmt.Errorf("shutdown script does not " +
|
|
|
|
"match upfront shutdown script")
|
2022-07-26 16:37:51 -07:00
|
|
|
|
|
|
|
// ErrProposalExeceedsMaxFee is returned when as the initiator, the
|
|
|
|
// latest fee proposal sent by the responder exceed our max fee.
|
|
|
|
// responder.
|
|
|
|
ErrProposalExeceedsMaxFee = fmt.Errorf("latest fee proposal exceeds " +
|
|
|
|
"max fee")
|
2022-06-10 11:17:20 -07:00
|
|
|
|
|
|
|
// ErrInvalidShutdownScript is returned when we receive an address from
|
|
|
|
// a peer that isn't either a p2wsh or p2tr address.
|
|
|
|
ErrInvalidShutdownScript = fmt.Errorf("invalid shutdown script")
|
2017-11-23 00:54:28 -06:00
|
|
|
)
|
|
|
|
|
|
|
|
// closeState represents all the possible states the channel closer state
|
|
|
|
// machine can be in. Each message will either advance to the next state, or
|
|
|
|
// remain at the current state. Once the state machine reaches a state of
|
|
|
|
// closeFinished, then negotiation is over.
|
|
|
|
type closeState uint8
|
|
|
|
|
|
|
|
const (
|
2018-04-18 05:03:27 +03:00
|
|
|
// closeIdle is the initial starting state. In this state, the state
|
|
|
|
// machine has been instantiated, but no state transitions have been
|
2020-06-16 20:13:28 -04:00
|
|
|
// attempted. If a state machine receives a message while in this state,
|
|
|
|
// then it is the responder to an initiated cooperative channel closure.
|
2017-11-23 00:54:28 -06:00
|
|
|
closeIdle closeState = iota
|
|
|
|
|
|
|
|
// closeShutdownInitiated is the state that's transitioned to once the
|
|
|
|
// initiator of a closing workflow sends the shutdown message. At this
|
2020-06-16 20:13:28 -04:00
|
|
|
// point, they're waiting for the remote party to respond with their own
|
|
|
|
// shutdown message. After which, they'll both enter the fee negotiation
|
|
|
|
// phase.
|
2017-11-23 00:54:28 -06:00
|
|
|
closeShutdownInitiated
|
|
|
|
|
|
|
|
// closeFeeNegotiation is the third, and most persistent state. Both
|
2018-04-18 05:03:27 +03:00
|
|
|
// parties enter this state after they've sent and received a shutdown
|
2017-11-23 00:54:28 -06:00
|
|
|
// message. During this phase, both sides will send monotonically
|
2020-06-16 20:13:28 -04:00
|
|
|
// increasing fee requests until one side accepts the last fee rate offered
|
|
|
|
// by the other party. In this case, the party will broadcast the closing
|
|
|
|
// transaction, and send the accepted fee to the remote party. This then
|
|
|
|
// causes a shift into the closeFinished state.
|
2017-11-23 00:54:28 -06:00
|
|
|
closeFeeNegotiation
|
|
|
|
|
2020-06-16 20:13:28 -04:00
|
|
|
// closeFinished is the final state of the state machine. In this state, a
|
|
|
|
// side has accepted a fee offer and has broadcast the valid closing
|
|
|
|
// transaction to the network. During this phase, the closing transaction
|
|
|
|
// becomes available for examination.
|
2017-11-23 00:54:28 -06:00
|
|
|
closeFinished
|
|
|
|
)
|
|
|
|
|
2022-07-26 16:37:51 -07:00
|
|
|
const (
|
|
|
|
// defaultMaxFeeMultiplier is a multiplier we'll apply to the ideal fee
|
|
|
|
// of the initiator, to decide when the negotiated fee is too high. By
|
|
|
|
// default, we want to bail out if we attempt to negotiate a fee that's
|
|
|
|
// 3x higher than our max fee.
|
|
|
|
defaultMaxFeeMultiplier = 3
|
|
|
|
)
|
|
|
|
|
2020-06-16 20:33:06 -04:00
|
|
|
// ChanCloseCfg holds all the items that a ChanCloser requires to carry out its
|
2020-06-16 20:13:28 -04:00
|
|
|
// duties.
|
2020-06-11 15:25:05 -04:00
|
|
|
type ChanCloseCfg struct {
|
|
|
|
// Channel is the channel that should be closed.
|
2022-07-26 16:39:19 -07:00
|
|
|
Channel Channel
|
2017-11-23 00:54:28 -06:00
|
|
|
|
2023-01-19 19:43:47 -08:00
|
|
|
// MusigSession is used to handle generating musig2 nonces, and also
|
|
|
|
// creating the proper set of closing options for taproot channels.
|
|
|
|
MusigSession MusigSession
|
|
|
|
|
2020-06-11 15:25:05 -04:00
|
|
|
// BroadcastTx broadcasts the passed transaction to the network.
|
|
|
|
BroadcastTx func(*wire.MsgTx, string) error
|
2017-11-23 00:54:28 -06:00
|
|
|
|
2020-06-11 15:25:05 -04:00
|
|
|
// DisableChannel disables a channel, resulting in it not being able to
|
2018-06-13 19:43:42 -07:00
|
|
|
// forward payments.
|
2020-06-11 15:25:05 -04:00
|
|
|
DisableChannel func(wire.OutPoint) error
|
2018-06-13 19:43:42 -07:00
|
|
|
|
2020-06-11 15:25:05 -04:00
|
|
|
// Disconnect will disconnect from the remote peer in this close.
|
|
|
|
Disconnect func() error
|
2019-12-03 11:38:29 +02:00
|
|
|
|
2022-07-26 16:37:51 -07:00
|
|
|
// MaxFee, is non-zero represents the highest fee that the initiator is
|
|
|
|
// willing to pay to close the channel.
|
|
|
|
MaxFee chainfee.SatPerKWeight
|
2022-07-26 16:39:19 -07:00
|
|
|
|
2022-06-10 11:17:20 -07:00
|
|
|
// ChainParams holds the parameters of the chain that we're active on.
|
|
|
|
ChainParams *chaincfg.Params
|
|
|
|
|
2020-06-11 15:25:05 -04:00
|
|
|
// Quit is a channel that should be sent upon in the occasion the state
|
2018-02-07 04:11:11 +01:00
|
|
|
// machine should cease all progress and shutdown.
|
2020-06-11 15:25:05 -04:00
|
|
|
Quit chan struct{}
|
lnwallet/chancloser: properly compute initial fee of cop close txn
In this commit, we modify the way we compute the starting ideal fee for
the co-op close transaction. Before thsi commit, channel.CalcFee was
used, which'll compute the fee based on the commitment transaction
itself, rathern than the co-op close transaction. As the co-op close
transaction is potentailly bigger (two P2TR outputs) than the commitment
transaction, this can cause us to under estimate the fee, which can
result in the fee rate being too low to propagate.
To remedy this, we now compute a fee estimate from scratch, based on the
delivery fees of the two parties.
We also add a bug fix in the chancloser unit tests that wasn't caught
due to loop variable shadowing.
The wallet import itest has been updated as well, since we'll now pay
600 extra saothis to close the channel, since we're accounting for the
added weight of the P2TR outputs.
Fixes #6953
2022-09-29 19:49:44 -07:00
|
|
|
|
|
|
|
// FeeEstimator is used to estimate the absolute starting co-op close
|
|
|
|
// fee.
|
|
|
|
FeeEstimator CoopFeeEstimator
|
2017-11-23 00:54:28 -06:00
|
|
|
}
|
|
|
|
|
2020-06-16 20:33:06 -04:00
|
|
|
// ChanCloser is a state machine that handles the cooperative channel closure
|
2020-06-16 20:13:28 -04:00
|
|
|
// procedure. This includes shutting down a channel, marking it ineligible for
|
|
|
|
// routing HTLC's, negotiating fees with the remote party, and finally
|
|
|
|
// broadcasting the fully signed closure transaction to the network.
|
2020-06-16 20:33:06 -04:00
|
|
|
type ChanCloser struct {
|
2017-11-23 00:54:28 -06:00
|
|
|
// state is the current state of the state machine.
|
|
|
|
state closeState
|
|
|
|
|
2020-06-16 20:33:06 -04:00
|
|
|
// cfg holds the configuration for this ChanCloser instance.
|
2020-06-11 15:25:05 -04:00
|
|
|
cfg ChanCloseCfg
|
2017-11-23 00:54:28 -06:00
|
|
|
|
|
|
|
// chanPoint is the full channel point of the target channel.
|
|
|
|
chanPoint wire.OutPoint
|
|
|
|
|
|
|
|
// cid is the full channel ID of the target channel.
|
|
|
|
cid lnwire.ChannelID
|
|
|
|
|
|
|
|
// negotiationHeight is the height that the fee negotiation begun at.
|
|
|
|
negotiationHeight uint32
|
|
|
|
|
2020-06-16 20:13:28 -04:00
|
|
|
// closingTx is the final, fully signed closing transaction. This will only
|
|
|
|
// be populated once the state machine shifts to the closeFinished state.
|
2017-11-23 00:54:28 -06:00
|
|
|
closingTx *wire.MsgTx
|
|
|
|
|
|
|
|
// idealFeeSat is the ideal fee that the state machine should initially
|
|
|
|
// offer when starting negotiation. This will be used as a baseline.
|
|
|
|
idealFeeSat btcutil.Amount
|
|
|
|
|
2022-07-26 16:37:51 -07:00
|
|
|
// maxFee is the highest fee the initiator is willing to pay to close
|
|
|
|
// out the channel. This is either a use specified value, or a default
|
|
|
|
// multiplier based of the initial starting ideal fee.
|
|
|
|
maxFee btcutil.Amount
|
|
|
|
|
lnwallet/chancloser: properly compute initial fee of cop close txn
In this commit, we modify the way we compute the starting ideal fee for
the co-op close transaction. Before thsi commit, channel.CalcFee was
used, which'll compute the fee based on the commitment transaction
itself, rathern than the co-op close transaction. As the co-op close
transaction is potentailly bigger (two P2TR outputs) than the commitment
transaction, this can cause us to under estimate the fee, which can
result in the fee rate being too low to propagate.
To remedy this, we now compute a fee estimate from scratch, based on the
delivery fees of the two parties.
We also add a bug fix in the chancloser unit tests that wasn't caught
due to loop variable shadowing.
The wallet import itest has been updated as well, since we'll now pay
600 extra saothis to close the channel, since we're accounting for the
added weight of the P2TR outputs.
Fixes #6953
2022-09-29 19:49:44 -07:00
|
|
|
// idealFeeRate is our ideal fee rate.
|
|
|
|
idealFeeRate chainfee.SatPerKWeight
|
|
|
|
|
2020-06-16 20:13:28 -04:00
|
|
|
// lastFeeProposal is the last fee that we proposed to the remote party.
|
|
|
|
// We'll use this as a pivot point to ratchet our next offer up, down, or
|
|
|
|
// simply accept the remote party's prior offer.
|
2017-11-23 00:54:28 -06:00
|
|
|
lastFeeProposal btcutil.Amount
|
|
|
|
|
2020-06-16 20:13:28 -04:00
|
|
|
// priorFeeOffers is a map that keeps track of all the proposed fees that
|
|
|
|
// we've offered during the fee negotiation. We use this map to cut the
|
|
|
|
// negotiation early if the remote party ever sends an offer that we've
|
|
|
|
// sent in the past. Once negotiation terminates, we can extract the prior
|
|
|
|
// signature of our accepted offer from this map.
|
2017-11-23 00:54:28 -06:00
|
|
|
//
|
|
|
|
// TODO(roasbeef): need to ensure if they broadcast w/ any of our prior
|
|
|
|
// sigs, we are aware of
|
|
|
|
priorFeeOffers map[btcutil.Amount]*lnwire.ClosingSigned
|
|
|
|
|
2020-06-16 20:13:28 -04:00
|
|
|
// closeReq is the initial closing request. This will only be populated if
|
|
|
|
// we're the initiator of this closing negotiation.
|
2017-11-23 00:54:28 -06:00
|
|
|
//
|
|
|
|
// TODO(roasbeef): abstract away
|
|
|
|
closeReq *htlcswitch.ChanClose
|
|
|
|
|
2020-06-16 20:13:28 -04:00
|
|
|
// localDeliveryScript is the script that we'll send our settled channel
|
|
|
|
// funds to.
|
2017-11-23 00:54:28 -06:00
|
|
|
localDeliveryScript []byte
|
|
|
|
|
2020-06-16 20:13:28 -04:00
|
|
|
// remoteDeliveryScript is the script that we'll send the remote party's
|
|
|
|
// settled channel funds to.
|
2017-11-23 00:54:28 -06:00
|
|
|
remoteDeliveryScript []byte
|
2020-02-21 13:24:23 +02:00
|
|
|
|
|
|
|
// locallyInitiated is true if we initiated the channel close.
|
|
|
|
locallyInitiated bool
|
2017-11-23 00:54:28 -06:00
|
|
|
}
|
|
|
|
|
lnwallet/chancloser: properly compute initial fee of cop close txn
In this commit, we modify the way we compute the starting ideal fee for
the co-op close transaction. Before thsi commit, channel.CalcFee was
used, which'll compute the fee based on the commitment transaction
itself, rathern than the co-op close transaction. As the co-op close
transaction is potentailly bigger (two P2TR outputs) than the commitment
transaction, this can cause us to under estimate the fee, which can
result in the fee rate being too low to propagate.
To remedy this, we now compute a fee estimate from scratch, based on the
delivery fees of the two parties.
We also add a bug fix in the chancloser unit tests that wasn't caught
due to loop variable shadowing.
The wallet import itest has been updated as well, since we'll now pay
600 extra saothis to close the channel, since we're accounting for the
added weight of the P2TR outputs.
Fixes #6953
2022-09-29 19:49:44 -07:00
|
|
|
// calcCoopCloseFee computes an "ideal" absolute co-op close fee given the
|
|
|
|
// delivery scripts of both parties and our ideal fee rate.
|
2023-08-17 16:09:56 -07:00
|
|
|
func calcCoopCloseFee(chanType channeldb.ChannelType,
|
|
|
|
localOutput, remoteOutput *wire.TxOut,
|
lnwallet/chancloser: properly compute initial fee of cop close txn
In this commit, we modify the way we compute the starting ideal fee for
the co-op close transaction. Before thsi commit, channel.CalcFee was
used, which'll compute the fee based on the commitment transaction
itself, rathern than the co-op close transaction. As the co-op close
transaction is potentailly bigger (two P2TR outputs) than the commitment
transaction, this can cause us to under estimate the fee, which can
result in the fee rate being too low to propagate.
To remedy this, we now compute a fee estimate from scratch, based on the
delivery fees of the two parties.
We also add a bug fix in the chancloser unit tests that wasn't caught
due to loop variable shadowing.
The wallet import itest has been updated as well, since we'll now pay
600 extra saothis to close the channel, since we're accounting for the
added weight of the P2TR outputs.
Fixes #6953
2022-09-29 19:49:44 -07:00
|
|
|
idealFeeRate chainfee.SatPerKWeight) btcutil.Amount {
|
|
|
|
|
|
|
|
var weightEstimator input.TxWeightEstimator
|
|
|
|
|
2023-08-17 16:09:56 -07:00
|
|
|
if chanType.IsTaproot() {
|
|
|
|
weightEstimator.AddWitnessInput(
|
|
|
|
input.TaprootSignatureWitnessSize,
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
weightEstimator.AddWitnessInput(input.MultiSigWitnessSize)
|
|
|
|
}
|
lnwallet/chancloser: properly compute initial fee of cop close txn
In this commit, we modify the way we compute the starting ideal fee for
the co-op close transaction. Before thsi commit, channel.CalcFee was
used, which'll compute the fee based on the commitment transaction
itself, rathern than the co-op close transaction. As the co-op close
transaction is potentailly bigger (two P2TR outputs) than the commitment
transaction, this can cause us to under estimate the fee, which can
result in the fee rate being too low to propagate.
To remedy this, we now compute a fee estimate from scratch, based on the
delivery fees of the two parties.
We also add a bug fix in the chancloser unit tests that wasn't caught
due to loop variable shadowing.
The wallet import itest has been updated as well, since we'll now pay
600 extra saothis to close the channel, since we're accounting for the
added weight of the P2TR outputs.
Fixes #6953
2022-09-29 19:49:44 -07:00
|
|
|
|
|
|
|
// One of these outputs might be dust, so we'll skip adding it to our
|
|
|
|
// mock transaction, so the fees are more accurate.
|
|
|
|
if localOutput != nil {
|
|
|
|
weightEstimator.AddTxOutput(localOutput)
|
|
|
|
}
|
|
|
|
if remoteOutput != nil {
|
|
|
|
weightEstimator.AddTxOutput(remoteOutput)
|
|
|
|
}
|
|
|
|
|
|
|
|
totalWeight := int64(weightEstimator.Weight())
|
|
|
|
|
|
|
|
return idealFeeRate.FeeForWeight(totalWeight)
|
|
|
|
}
|
|
|
|
|
|
|
|
// SimpleCoopFeeEstimator is the default co-op close fee estimator. It assumes
|
|
|
|
// a normal segwit v0 channel, and that no outputs on the closing transaction
|
|
|
|
// are dust.
|
|
|
|
type SimpleCoopFeeEstimator struct {
|
|
|
|
}
|
|
|
|
|
|
|
|
// EstimateFee estimates an _absolute_ fee for a co-op close transaction given
|
|
|
|
// the local+remote tx outs (for the co-op close transaction), channel type,
|
|
|
|
// and ideal fee rate.
|
|
|
|
func (d *SimpleCoopFeeEstimator) EstimateFee(chanType channeldb.ChannelType,
|
|
|
|
localTxOut, remoteTxOut *wire.TxOut,
|
|
|
|
idealFeeRate chainfee.SatPerKWeight) btcutil.Amount {
|
|
|
|
|
2023-08-17 16:09:56 -07:00
|
|
|
return calcCoopCloseFee(chanType, localTxOut, remoteTxOut, idealFeeRate)
|
lnwallet/chancloser: properly compute initial fee of cop close txn
In this commit, we modify the way we compute the starting ideal fee for
the co-op close transaction. Before thsi commit, channel.CalcFee was
used, which'll compute the fee based on the commitment transaction
itself, rathern than the co-op close transaction. As the co-op close
transaction is potentailly bigger (two P2TR outputs) than the commitment
transaction, this can cause us to under estimate the fee, which can
result in the fee rate being too low to propagate.
To remedy this, we now compute a fee estimate from scratch, based on the
delivery fees of the two parties.
We also add a bug fix in the chancloser unit tests that wasn't caught
due to loop variable shadowing.
The wallet import itest has been updated as well, since we'll now pay
600 extra saothis to close the channel, since we're accounting for the
added weight of the P2TR outputs.
Fixes #6953
2022-09-29 19:49:44 -07:00
|
|
|
}
|
|
|
|
|
2020-06-16 20:13:28 -04:00
|
|
|
// NewChanCloser creates a new instance of the channel closure given the passed
|
|
|
|
// configuration, and delivery+fee preference. The final argument should only
|
|
|
|
// be populated iff, we're the initiator of this closing request.
|
2020-06-11 15:25:05 -04:00
|
|
|
func NewChanCloser(cfg ChanCloseCfg, deliveryScript []byte,
|
2019-10-30 19:43:05 -07:00
|
|
|
idealFeePerKw chainfee.SatPerKWeight, negotiationHeight uint32,
|
2020-06-16 20:33:06 -04:00
|
|
|
closeReq *htlcswitch.ChanClose, locallyInitiated bool) *ChanCloser {
|
2017-11-23 00:54:28 -06:00
|
|
|
|
2020-06-11 15:25:05 -04:00
|
|
|
cid := lnwire.NewChanIDFromOutPoint(cfg.Channel.ChannelPoint())
|
2020-06-16 20:33:06 -04:00
|
|
|
return &ChanCloser{
|
2017-11-23 00:54:28 -06:00
|
|
|
closeReq: closeReq,
|
|
|
|
state: closeIdle,
|
2020-06-11 15:25:05 -04:00
|
|
|
chanPoint: *cfg.Channel.ChannelPoint(),
|
2017-11-23 00:54:28 -06:00
|
|
|
cid: cid,
|
|
|
|
cfg: cfg,
|
|
|
|
negotiationHeight: negotiationHeight,
|
lnwallet/chancloser: properly compute initial fee of cop close txn
In this commit, we modify the way we compute the starting ideal fee for
the co-op close transaction. Before thsi commit, channel.CalcFee was
used, which'll compute the fee based on the commitment transaction
itself, rathern than the co-op close transaction. As the co-op close
transaction is potentailly bigger (two P2TR outputs) than the commitment
transaction, this can cause us to under estimate the fee, which can
result in the fee rate being too low to propagate.
To remedy this, we now compute a fee estimate from scratch, based on the
delivery fees of the two parties.
We also add a bug fix in the chancloser unit tests that wasn't caught
due to loop variable shadowing.
The wallet import itest has been updated as well, since we'll now pay
600 extra saothis to close the channel, since we're accounting for the
added weight of the P2TR outputs.
Fixes #6953
2022-09-29 19:49:44 -07:00
|
|
|
idealFeeRate: idealFeePerKw,
|
2017-11-23 00:54:28 -06:00
|
|
|
localDeliveryScript: deliveryScript,
|
|
|
|
priorFeeOffers: make(map[btcutil.Amount]*lnwire.ClosingSigned),
|
2020-02-21 13:24:23 +02:00
|
|
|
locallyInitiated: locallyInitiated,
|
2017-11-23 00:54:28 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
lnwallet/chancloser: properly compute initial fee of cop close txn
In this commit, we modify the way we compute the starting ideal fee for
the co-op close transaction. Before thsi commit, channel.CalcFee was
used, which'll compute the fee based on the commitment transaction
itself, rathern than the co-op close transaction. As the co-op close
transaction is potentailly bigger (two P2TR outputs) than the commitment
transaction, this can cause us to under estimate the fee, which can
result in the fee rate being too low to propagate.
To remedy this, we now compute a fee estimate from scratch, based on the
delivery fees of the two parties.
We also add a bug fix in the chancloser unit tests that wasn't caught
due to loop variable shadowing.
The wallet import itest has been updated as well, since we'll now pay
600 extra saothis to close the channel, since we're accounting for the
added weight of the P2TR outputs.
Fixes #6953
2022-09-29 19:49:44 -07:00
|
|
|
// initFeeBaseline computes our ideal fee rate, and also the largest fee we'll
|
|
|
|
// accept given information about the delivery script of the remote party.
|
|
|
|
func (c *ChanCloser) initFeeBaseline() {
|
|
|
|
// Depending on if a balance ends up being dust or not, we'll pass a
|
|
|
|
// nil TxOut into the EstimateFee call which can handle it.
|
|
|
|
var localTxOut, remoteTxOut *wire.TxOut
|
|
|
|
if !c.cfg.Channel.LocalBalanceDust() {
|
|
|
|
localTxOut = &wire.TxOut{
|
|
|
|
PkScript: c.localDeliveryScript,
|
|
|
|
Value: 0,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if !c.cfg.Channel.RemoteBalanceDust() {
|
|
|
|
remoteTxOut = &wire.TxOut{
|
|
|
|
PkScript: c.remoteDeliveryScript,
|
|
|
|
Value: 0,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Given the target fee-per-kw, we'll compute what our ideal _total_
|
|
|
|
// fee will be starting at for this fee negotiation.
|
|
|
|
c.idealFeeSat = c.cfg.FeeEstimator.EstimateFee(
|
|
|
|
0, localTxOut, remoteTxOut, c.idealFeeRate,
|
|
|
|
)
|
|
|
|
|
|
|
|
// When we're the initiator, we'll want to also factor in the highest
|
|
|
|
// fee we want to pay. This'll either be 3x the ideal fee, or the
|
|
|
|
// specified explicit max fee.
|
|
|
|
c.maxFee = c.idealFeeSat * defaultMaxFeeMultiplier
|
|
|
|
if c.cfg.MaxFee > 0 {
|
|
|
|
c.maxFee = c.cfg.FeeEstimator.EstimateFee(
|
|
|
|
0, localTxOut, remoteTxOut, c.cfg.MaxFee,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
chancloserLog.Infof("Ideal fee for closure of ChannelPoint(%v) "+
|
|
|
|
"is: %v sat (max_fee=%v sat)", c.cfg.Channel.ChannelPoint(),
|
|
|
|
int64(c.idealFeeSat), int64(c.maxFee))
|
|
|
|
}
|
|
|
|
|
2018-04-18 05:03:27 +03:00
|
|
|
// initChanShutdown begins the shutdown process by un-registering the channel,
|
2017-11-23 00:54:28 -06:00
|
|
|
// and creating a valid shutdown message to our target delivery address.
|
2020-06-16 20:33:06 -04:00
|
|
|
func (c *ChanCloser) initChanShutdown() (*lnwire.Shutdown, error) {
|
2020-06-16 20:13:28 -04:00
|
|
|
// With both items constructed we'll now send the shutdown message for this
|
|
|
|
// particular channel, advertising a shutdown request to our desired
|
|
|
|
// closing script.
|
2017-11-23 00:54:28 -06:00
|
|
|
shutdown := lnwire.NewShutdown(c.cid, c.localDeliveryScript)
|
|
|
|
|
2023-01-19 19:43:47 -08:00
|
|
|
// If this is a taproot channel, then we'll need to also generate a
|
|
|
|
// nonce that'll be used sign the co-op close transaction offer.
|
|
|
|
if c.cfg.Channel.ChanType().IsTaproot() {
|
|
|
|
firstClosingNonce, err := c.cfg.MusigSession.ClosingNonce()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
shutdown.ShutdownNonce = (*lnwire.ShutdownNonce)(
|
|
|
|
&firstClosingNonce.PubNonce,
|
|
|
|
)
|
|
|
|
|
|
|
|
chancloserLog.Infof("Initiating shutdown w/ nonce: %v",
|
|
|
|
spew.Sdump(firstClosingNonce.PubNonce))
|
|
|
|
}
|
|
|
|
|
|
|
|
// Before closing, we'll attempt to send a disable update for the
|
|
|
|
// channel. We do so before closing the channel as otherwise the
|
|
|
|
// current edge policy won't be retrievable from the graph.
|
2020-06-11 15:25:05 -04:00
|
|
|
if err := c.cfg.DisableChannel(c.chanPoint); err != nil {
|
2020-06-16 20:33:06 -04:00
|
|
|
chancloserLog.Warnf("Unable to disable channel %v on close: %v",
|
2020-06-16 20:13:28 -04:00
|
|
|
c.chanPoint, err)
|
2019-12-04 13:31:03 -08:00
|
|
|
}
|
|
|
|
|
2023-01-19 19:43:47 -08:00
|
|
|
// Before continuing, mark the channel as cooperatively closed with a
|
|
|
|
// nil txn. Even though we haven't negotiated the final txn, this
|
|
|
|
// guarantees that our listchannels rpc will be externally consistent,
|
|
|
|
// and reflect that the channel is being shutdown by the time the
|
|
|
|
// closing request returns.
|
2020-06-11 15:25:05 -04:00
|
|
|
err := c.cfg.Channel.MarkCoopBroadcasted(nil, c.locallyInitiated)
|
2019-12-04 13:31:03 -08:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2020-06-16 20:33:06 -04:00
|
|
|
chancloserLog.Infof("ChannelPoint(%v): sending shutdown message",
|
|
|
|
c.chanPoint)
|
2017-11-23 00:54:28 -06:00
|
|
|
|
|
|
|
return shutdown, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// ShutdownChan is the first method that's to be called by the initiator of the
|
2018-04-18 05:03:27 +03:00
|
|
|
// cooperative channel closure. This message returns the shutdown message to
|
|
|
|
// send to the remote party. Upon completion, we enter the
|
2017-11-23 00:54:28 -06:00
|
|
|
// closeShutdownInitiated phase as we await a response.
|
2020-06-16 20:33:06 -04:00
|
|
|
func (c *ChanCloser) ShutdownChan() (*lnwire.Shutdown, error) {
|
2020-06-16 20:13:28 -04:00
|
|
|
// If we attempt to shutdown the channel for the first time, and we're not
|
|
|
|
// in the closeIdle state, then the caller made an error.
|
2017-11-23 00:54:28 -06:00
|
|
|
if c.state != closeIdle {
|
|
|
|
return nil, ErrChanAlreadyClosing
|
|
|
|
}
|
|
|
|
|
2020-06-16 20:33:06 -04:00
|
|
|
chancloserLog.Infof("ChannelPoint(%v): initiating shutdown", c.chanPoint)
|
2017-11-23 00:54:28 -06:00
|
|
|
|
|
|
|
shutdownMsg, err := c.initChanShutdown()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// With the opening steps complete, we'll transition into the
|
2020-06-16 20:13:28 -04:00
|
|
|
// closeShutdownInitiated state. In this state, we'll wait until the other
|
|
|
|
// party sends their version of the shutdown message.
|
2017-11-23 00:54:28 -06:00
|
|
|
c.state = closeShutdownInitiated
|
|
|
|
|
2020-06-16 20:13:28 -04:00
|
|
|
// Finally, we'll return the shutdown message to the caller so it can send
|
|
|
|
// it to the remote peer.
|
2017-11-23 00:54:28 -06:00
|
|
|
return shutdownMsg, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// ClosingTx returns the fully signed, final closing transaction.
|
|
|
|
//
|
2018-02-07 04:11:11 +01:00
|
|
|
// NOTE: This transaction is only available if the state machine is in the
|
2017-11-23 00:54:28 -06:00
|
|
|
// closeFinished state.
|
2020-06-16 20:33:06 -04:00
|
|
|
func (c *ChanCloser) ClosingTx() (*wire.MsgTx, error) {
|
2020-06-16 20:13:28 -04:00
|
|
|
// If the state machine hasn't finished closing the channel, then we'll
|
2017-11-23 00:54:28 -06:00
|
|
|
// return an error as we haven't yet computed the closing tx.
|
|
|
|
if c.state != closeFinished {
|
|
|
|
return nil, ErrChanCloseNotFinished
|
|
|
|
}
|
|
|
|
|
|
|
|
return c.closingTx, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// CloseRequest returns the original close request that prompted the creation
|
|
|
|
// of the state machine.
|
|
|
|
//
|
|
|
|
// NOTE: This will only return a non-nil pointer if we were the initiator of
|
|
|
|
// the cooperative closure workflow.
|
2020-06-16 20:33:06 -04:00
|
|
|
func (c *ChanCloser) CloseRequest() *htlcswitch.ChanClose {
|
2017-11-23 00:54:28 -06:00
|
|
|
return c.closeReq
|
|
|
|
}
|
|
|
|
|
2022-07-26 16:39:19 -07:00
|
|
|
// Channel returns the channel stored in the config as a
|
|
|
|
// *lnwallet.LightningChannel.
|
|
|
|
//
|
|
|
|
// NOTE: This method will PANIC if the underlying channel implementation isn't
|
|
|
|
// the desired type.
|
2020-06-16 20:33:06 -04:00
|
|
|
func (c *ChanCloser) Channel() *lnwallet.LightningChannel {
|
2023-01-19 19:43:47 -08:00
|
|
|
// TODO(roasbeef): remove this
|
2022-07-26 16:39:19 -07:00
|
|
|
return c.cfg.Channel.(*lnwallet.LightningChannel)
|
2020-06-11 15:25:05 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// NegotiationHeight returns the negotiation height.
|
2020-06-16 20:33:06 -04:00
|
|
|
func (c *ChanCloser) NegotiationHeight() uint32 {
|
2020-06-11 15:25:05 -04:00
|
|
|
return c.negotiationHeight
|
|
|
|
}
|
|
|
|
|
2022-06-10 11:17:20 -07:00
|
|
|
// validateShutdownScript attempts to match and validate the script provided in
|
|
|
|
// our peer's shutdown message with the upfront shutdown script we have on
|
|
|
|
// record. For any script specified, we also make sure it matches our
|
|
|
|
// requirements. If no upfront shutdown script was set, we do not need to
|
|
|
|
// enforce option upfront shutdown, so the function returns early. If an
|
|
|
|
// upfront script is set, we check whether it matches the script provided by
|
|
|
|
// our peer. If they do not match, we use the disconnect function provided to
|
|
|
|
// disconnect from the peer.
|
|
|
|
func validateShutdownScript(disconnect func() error, upfrontScript,
|
|
|
|
peerScript lnwire.DeliveryAddress, netParams *chaincfg.Params) error {
|
|
|
|
|
|
|
|
// Either way, we'll make sure that the script passed meets our
|
|
|
|
// standards. The upfrontScript should have already been checked at an
|
|
|
|
// earlier stage, but we'll repeat the check here for defense in depth.
|
|
|
|
if len(upfrontScript) != 0 {
|
|
|
|
if !lnwallet.ValidateUpfrontShutdown(upfrontScript, netParams) {
|
|
|
|
return ErrInvalidShutdownScript
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if len(peerScript) != 0 {
|
|
|
|
if !lnwallet.ValidateUpfrontShutdown(peerScript, netParams) {
|
|
|
|
return ErrInvalidShutdownScript
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// If no upfront shutdown script was set, return early because we do
|
|
|
|
// not need to enforce closure to a specific script.
|
2019-12-03 11:38:29 +02:00
|
|
|
if len(upfrontScript) == 0 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// If an upfront shutdown script was provided, disconnect from the peer, as
|
|
|
|
// per BOLT 2, and return an error.
|
|
|
|
if !bytes.Equal(upfrontScript, peerScript) {
|
2020-06-16 20:33:06 -04:00
|
|
|
chancloserLog.Warnf("peer's script: %x does not match upfront "+
|
2019-12-03 11:38:29 +02:00
|
|
|
"shutdown script: %x", peerScript, upfrontScript)
|
|
|
|
|
|
|
|
// Disconnect from the peer because they have violated option upfront
|
|
|
|
// shutdown.
|
|
|
|
if err := disconnect(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-06-11 15:25:05 -04:00
|
|
|
return ErrUpfrontShutdownScriptMismatch
|
2019-12-03 11:38:29 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2017-11-23 00:54:28 -06:00
|
|
|
// ProcessCloseMsg attempts to process the next message in the closing series.
|
|
|
|
// This method will update the state accordingly and return two primary values:
|
|
|
|
// the next set of messages to be sent, and a bool indicating if the fee
|
|
|
|
// negotiation process has completed. If the second value is true, then this
|
2020-06-16 20:33:06 -04:00
|
|
|
// means the ChanCloser can be garbage collected.
|
|
|
|
func (c *ChanCloser) ProcessCloseMsg(msg lnwire.Message) ([]lnwire.Message,
|
2020-06-16 20:13:28 -04:00
|
|
|
bool, error) {
|
|
|
|
|
2017-11-23 00:54:28 -06:00
|
|
|
switch c.state {
|
2020-06-16 20:13:28 -04:00
|
|
|
// If we're in the close idle state, and we're receiving a channel closure
|
|
|
|
// related message, then this indicates that we're on the receiving side of
|
|
|
|
// an initiated channel closure.
|
2017-11-23 00:54:28 -06:00
|
|
|
case closeIdle:
|
|
|
|
// First, we'll assert that we have a channel shutdown message,
|
2020-06-16 20:13:28 -04:00
|
|
|
// as otherwise, this is an attempted invalid state transition.
|
|
|
|
shutdownMsg, ok := msg.(*lnwire.Shutdown)
|
2017-11-23 00:54:28 -06:00
|
|
|
if !ok {
|
2023-01-19 19:43:47 -08:00
|
|
|
return nil, false, fmt.Errorf("expected "+
|
|
|
|
"lnwire.Shutdown, instead have %v",
|
|
|
|
spew.Sdump(msg))
|
2017-11-23 00:54:28 -06:00
|
|
|
}
|
|
|
|
|
2020-07-01 17:45:59 -07:00
|
|
|
// As we're the responder to this shutdown (the other party
|
|
|
|
// wants to close), we'll check if this is a frozen channel or
|
|
|
|
// not. If the channel is frozen and we were not also the
|
|
|
|
// initiator of the channel opening, then we'll deny their close
|
|
|
|
// attempt.
|
2020-06-11 15:25:05 -04:00
|
|
|
chanInitiator := c.cfg.Channel.IsInitiator()
|
2020-07-01 17:45:59 -07:00
|
|
|
if !chanInitiator {
|
2022-07-26 16:39:19 -07:00
|
|
|
absoluteThawHeight, err := c.cfg.Channel.AbsoluteThawHeight()
|
2020-07-01 17:45:59 -07:00
|
|
|
if err != nil {
|
|
|
|
return nil, false, err
|
|
|
|
}
|
|
|
|
if c.negotiationHeight < absoluteThawHeight {
|
|
|
|
return nil, false, fmt.Errorf("initiator "+
|
|
|
|
"attempting to co-op close frozen "+
|
|
|
|
"ChannelPoint(%v) (current_height=%v, "+
|
|
|
|
"thaw_height=%v)", c.chanPoint,
|
|
|
|
c.negotiationHeight, absoluteThawHeight)
|
|
|
|
}
|
2020-03-13 16:58:05 -07:00
|
|
|
}
|
|
|
|
|
2019-12-03 11:38:29 +02:00
|
|
|
// If the remote node opened the channel with option upfront shutdown
|
|
|
|
// script, check that the script they provided matches.
|
2022-06-10 11:17:20 -07:00
|
|
|
if err := validateShutdownScript(
|
2020-06-11 15:25:05 -04:00
|
|
|
c.cfg.Disconnect, c.cfg.Channel.RemoteUpfrontShutdownScript(),
|
2022-06-10 11:17:20 -07:00
|
|
|
shutdownMsg.Address, c.cfg.ChainParams,
|
2019-12-03 11:38:29 +02:00
|
|
|
); err != nil {
|
|
|
|
return nil, false, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Once we have checked that the other party has not violated option
|
|
|
|
// upfront shutdown we set their preference for delivery address. We'll
|
|
|
|
// use this when we craft the closure transaction.
|
2020-06-16 20:13:28 -04:00
|
|
|
c.remoteDeliveryScript = shutdownMsg.Address
|
2017-11-23 00:54:28 -06:00
|
|
|
|
lnwallet/chancloser: properly compute initial fee of cop close txn
In this commit, we modify the way we compute the starting ideal fee for
the co-op close transaction. Before thsi commit, channel.CalcFee was
used, which'll compute the fee based on the commitment transaction
itself, rathern than the co-op close transaction. As the co-op close
transaction is potentailly bigger (two P2TR outputs) than the commitment
transaction, this can cause us to under estimate the fee, which can
result in the fee rate being too low to propagate.
To remedy this, we now compute a fee estimate from scratch, based on the
delivery fees of the two parties.
We also add a bug fix in the chancloser unit tests that wasn't caught
due to loop variable shadowing.
The wallet import itest has been updated as well, since we'll now pay
600 extra saothis to close the channel, since we're accounting for the
added weight of the P2TR outputs.
Fixes #6953
2022-09-29 19:49:44 -07:00
|
|
|
// Now that we know their desried delivery script, we can
|
|
|
|
// compute what our max/ideal fee will be.
|
|
|
|
c.initFeeBaseline()
|
|
|
|
|
2020-06-16 20:13:28 -04:00
|
|
|
// We'll generate a shutdown message of our own to send across the
|
|
|
|
// wire.
|
2017-11-23 00:54:28 -06:00
|
|
|
localShutdown, err := c.initChanShutdown()
|
|
|
|
if err != nil {
|
|
|
|
return nil, false, err
|
|
|
|
}
|
|
|
|
|
2023-01-19 19:43:47 -08:00
|
|
|
// If this is a taproot channel, then we'll want to stash the
|
|
|
|
// remote nonces so we can properly create a new musig
|
|
|
|
// session for signing.
|
|
|
|
if c.cfg.Channel.ChanType().IsTaproot() {
|
|
|
|
if shutdownMsg.ShutdownNonce == nil {
|
|
|
|
return nil, false, fmt.Errorf("shutdown " +
|
|
|
|
"nonce not populated")
|
|
|
|
}
|
|
|
|
|
|
|
|
c.cfg.MusigSession.InitRemoteNonce(&musig2.Nonces{
|
|
|
|
PubNonce: *shutdownMsg.ShutdownNonce,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2020-06-16 20:33:06 -04:00
|
|
|
chancloserLog.Infof("ChannelPoint(%v): responding to shutdown",
|
2017-11-23 00:54:28 -06:00
|
|
|
c.chanPoint)
|
|
|
|
|
2018-02-08 19:43:03 -08:00
|
|
|
msgsToSend := make([]lnwire.Message, 0, 2)
|
|
|
|
msgsToSend = append(msgsToSend, localShutdown)
|
2017-11-23 00:54:28 -06:00
|
|
|
|
2020-06-16 20:13:28 -04:00
|
|
|
// After the other party receives this message, we'll actually start
|
|
|
|
// the final stage of the closure process: fee negotiation. So we'll
|
|
|
|
// update our internal state to reflect this, so we can handle the next
|
|
|
|
// message sent.
|
2017-11-23 00:54:28 -06:00
|
|
|
c.state = closeFeeNegotiation
|
|
|
|
|
2020-06-16 20:13:28 -04:00
|
|
|
// We'll also craft our initial close proposal in order to keep the
|
|
|
|
// negotiation moving, but only if we're the negotiator.
|
2020-03-13 16:58:05 -07:00
|
|
|
if chanInitiator {
|
2018-02-08 19:43:03 -08:00
|
|
|
closeSigned, err := c.proposeCloseSigned(c.idealFeeSat)
|
|
|
|
if err != nil {
|
2023-01-19 19:43:47 -08:00
|
|
|
return nil, false, fmt.Errorf("unable to sign "+
|
|
|
|
"new co op close offer: %w", err)
|
2018-02-08 19:43:03 -08:00
|
|
|
}
|
|
|
|
msgsToSend = append(msgsToSend, closeSigned)
|
2017-11-23 00:54:28 -06:00
|
|
|
}
|
|
|
|
|
2020-06-16 20:13:28 -04:00
|
|
|
// We'll return both sets of messages to send to the remote party to
|
|
|
|
// kick off the fee negotiation process.
|
2017-11-23 00:54:28 -06:00
|
|
|
return msgsToSend, false, nil
|
|
|
|
|
2020-06-16 20:13:28 -04:00
|
|
|
// If we just initiated a channel shutdown, and we receive a new message,
|
|
|
|
// then this indicates the other party is ready to shutdown as well. In
|
|
|
|
// this state we'll send our first signature.
|
2017-11-23 00:54:28 -06:00
|
|
|
case closeShutdownInitiated:
|
2020-06-16 20:13:28 -04:00
|
|
|
// First, we'll assert that we have a channel shutdown message.
|
|
|
|
// Otherwise, this is an attempted invalid state transition.
|
|
|
|
shutdownMsg, ok := msg.(*lnwire.Shutdown)
|
2017-11-23 00:54:28 -06:00
|
|
|
if !ok {
|
2020-06-16 20:13:28 -04:00
|
|
|
return nil, false, fmt.Errorf("expected lnwire.Shutdown, instead "+
|
|
|
|
"have %v", spew.Sdump(msg))
|
2017-11-23 00:54:28 -06:00
|
|
|
}
|
|
|
|
|
2019-12-03 11:38:29 +02:00
|
|
|
// If the remote node opened the channel with option upfront shutdown
|
|
|
|
// script, check that the script they provided matches.
|
2022-06-10 11:17:20 -07:00
|
|
|
if err := validateShutdownScript(
|
|
|
|
c.cfg.Disconnect,
|
2020-06-16 20:13:28 -04:00
|
|
|
c.cfg.Channel.RemoteUpfrontShutdownScript(), shutdownMsg.Address,
|
2022-06-10 11:17:20 -07:00
|
|
|
c.cfg.ChainParams,
|
2019-12-03 11:38:29 +02:00
|
|
|
); err != nil {
|
|
|
|
return nil, false, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Now that we know this is a valid shutdown message and address, we'll
|
2017-11-23 00:54:28 -06:00
|
|
|
// record their preferred delivery closing script.
|
2020-06-16 20:13:28 -04:00
|
|
|
c.remoteDeliveryScript = shutdownMsg.Address
|
2017-11-23 00:54:28 -06:00
|
|
|
|
2020-06-16 20:13:28 -04:00
|
|
|
// At this point, we can now start the fee negotiation state, by
|
|
|
|
// constructing and sending our initial signature for what we think the
|
|
|
|
// closing transaction should look like.
|
2017-11-23 00:54:28 -06:00
|
|
|
c.state = closeFeeNegotiation
|
|
|
|
|
2023-01-19 19:43:47 -08:00
|
|
|
// Now that we know their desired delivery script, we can
|
lnwallet/chancloser: properly compute initial fee of cop close txn
In this commit, we modify the way we compute the starting ideal fee for
the co-op close transaction. Before thsi commit, channel.CalcFee was
used, which'll compute the fee based on the commitment transaction
itself, rathern than the co-op close transaction. As the co-op close
transaction is potentailly bigger (two P2TR outputs) than the commitment
transaction, this can cause us to under estimate the fee, which can
result in the fee rate being too low to propagate.
To remedy this, we now compute a fee estimate from scratch, based on the
delivery fees of the two parties.
We also add a bug fix in the chancloser unit tests that wasn't caught
due to loop variable shadowing.
The wallet import itest has been updated as well, since we'll now pay
600 extra saothis to close the channel, since we're accounting for the
added weight of the P2TR outputs.
Fixes #6953
2022-09-29 19:49:44 -07:00
|
|
|
// compute what our max/ideal fee will be.
|
|
|
|
c.initFeeBaseline()
|
|
|
|
|
2023-01-19 19:43:47 -08:00
|
|
|
// If this is a taproot channel, then we'll want to stash the
|
|
|
|
// local+remote nonces so we can properly create a new musig
|
|
|
|
// session for signing.
|
|
|
|
if c.cfg.Channel.ChanType().IsTaproot() {
|
|
|
|
if shutdownMsg.ShutdownNonce == nil {
|
|
|
|
return nil, false, fmt.Errorf("shutdown " +
|
|
|
|
"nonce not populated")
|
|
|
|
}
|
|
|
|
|
|
|
|
c.cfg.MusigSession.InitRemoteNonce(&musig2.Nonces{
|
|
|
|
PubNonce: *shutdownMsg.ShutdownNonce,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2020-06-16 20:33:06 -04:00
|
|
|
chancloserLog.Infof("ChannelPoint(%v): shutdown response received, "+
|
2017-11-23 00:54:28 -06:00
|
|
|
"entering fee negotiation", c.chanPoint)
|
|
|
|
|
2020-06-16 20:13:28 -04:00
|
|
|
// Starting with our ideal fee rate, we'll create an initial closing
|
|
|
|
// proposal, but only if we're the initiator, as otherwise, the other
|
|
|
|
// party will send their initial proposal first.
|
2020-06-11 15:25:05 -04:00
|
|
|
if c.cfg.Channel.IsInitiator() {
|
2018-02-08 19:43:03 -08:00
|
|
|
closeSigned, err := c.proposeCloseSigned(c.idealFeeSat)
|
|
|
|
if err != nil {
|
2023-01-19 19:43:47 -08:00
|
|
|
return nil, false, fmt.Errorf("unable to sign "+
|
|
|
|
"new co op close offer: %w", err)
|
2018-02-08 19:43:03 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
return []lnwire.Message{closeSigned}, false, nil
|
2017-11-23 00:54:28 -06:00
|
|
|
}
|
|
|
|
|
2018-02-08 19:43:03 -08:00
|
|
|
return nil, false, nil
|
2017-11-23 00:54:28 -06:00
|
|
|
|
2020-06-16 20:13:28 -04:00
|
|
|
// If we're receiving a message while we're in the fee negotiation phase,
|
|
|
|
// then this indicates the remote party is responding to a close signed
|
|
|
|
// message we sent, or kicking off the process with their own.
|
2017-11-23 00:54:28 -06:00
|
|
|
case closeFeeNegotiation:
|
2020-06-16 20:13:28 -04:00
|
|
|
// First, we'll assert that we're actually getting a ClosingSigned
|
|
|
|
// message, otherwise an invalid state transition was attempted.
|
2017-11-23 00:54:28 -06:00
|
|
|
closeSignedMsg, ok := msg.(*lnwire.ClosingSigned)
|
|
|
|
if !ok {
|
|
|
|
return nil, false, fmt.Errorf("expected lnwire.ClosingSigned, "+
|
|
|
|
"instead have %v", spew.Sdump(msg))
|
|
|
|
}
|
|
|
|
|
2023-01-19 19:43:47 -08:00
|
|
|
// If this is a taproot channel, then it MUST have a partial
|
|
|
|
// signature set at this point.
|
|
|
|
isTaproot := c.cfg.Channel.ChanType().IsTaproot()
|
|
|
|
if isTaproot && closeSignedMsg.PartialSig == nil {
|
|
|
|
return nil, false, fmt.Errorf("partial sig not set " +
|
|
|
|
"for taproot chan")
|
|
|
|
}
|
|
|
|
|
|
|
|
isInitiator := c.cfg.Channel.IsInitiator()
|
|
|
|
|
|
|
|
// We'll compare the proposed total fee, to what we've proposed
|
|
|
|
// during the negotiations. If it doesn't match any of our
|
|
|
|
// prior offers, then we'll attempt to ratchet the fee closer
|
|
|
|
// to our ideal fee.
|
2017-11-23 00:54:28 -06:00
|
|
|
remoteProposedFee := closeSignedMsg.FeeSatoshis
|
2023-01-19 19:43:47 -08:00
|
|
|
|
|
|
|
_, feeMatchesOffer := c.priorFeeOffers[remoteProposedFee]
|
|
|
|
switch {
|
|
|
|
// For taproot channels, since nonces are involved, we can't do
|
|
|
|
// the existing co-op close negotiation process without going
|
|
|
|
// to a fully round based model. Rather than do this, we'll
|
|
|
|
// just accept the very first offer by the initiator.
|
|
|
|
case isTaproot && !isInitiator:
|
|
|
|
chancloserLog.Infof("ChannelPoint(%v) accepting "+
|
|
|
|
"initiator fee of %v", c.chanPoint,
|
|
|
|
remoteProposedFee)
|
|
|
|
|
|
|
|
// To auto-accept the initiators proposal, we'll just
|
|
|
|
// send back a signature w/ the same offer. We don't
|
|
|
|
// send the message here, as we can drop down and
|
|
|
|
// finalize the closure and broadcast, then echo back
|
|
|
|
// to Alice the final signature.
|
|
|
|
_, err := c.proposeCloseSigned(remoteProposedFee)
|
|
|
|
if err != nil {
|
|
|
|
return nil, false, fmt.Errorf("unable to sign "+
|
|
|
|
"new co op close offer: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Otherwise, if we are the initiator, and we just sent a
|
|
|
|
// signature for a taproot channel, then we'll ensure that the
|
|
|
|
// fee rate matches up exactly.
|
|
|
|
case isTaproot && isInitiator && !feeMatchesOffer:
|
|
|
|
return nil, false, fmt.Errorf("fee rate for "+
|
|
|
|
"taproot channels was not accepted: "+
|
|
|
|
"sent %v, got %v",
|
|
|
|
c.idealFeeSat, remoteProposedFee)
|
|
|
|
|
|
|
|
// If we're the initiator of the taproot channel, and we had
|
|
|
|
// our fee echo'd back, then it's all good, and we can proceed
|
|
|
|
// with final broadcast.
|
|
|
|
case isTaproot && isInitiator && feeMatchesOffer:
|
|
|
|
break
|
|
|
|
|
|
|
|
// Otherwise, if this is a normal segwit v0 channel, and the
|
|
|
|
// fee doesn't match our offer, then we'll try to "negotiate" a
|
|
|
|
// new fee.
|
|
|
|
case !feeMatchesOffer:
|
|
|
|
// We'll now attempt to ratchet towards a fee deemed
|
|
|
|
// acceptable by both parties, factoring in our ideal
|
|
|
|
// fee rate, and the last proposed fee by both sides.
|
2020-06-16 20:33:06 -04:00
|
|
|
feeProposal := calcCompromiseFee(c.chanPoint, c.idealFeeSat,
|
|
|
|
c.lastFeeProposal, remoteProposedFee,
|
2017-11-23 00:54:28 -06:00
|
|
|
)
|
2022-07-26 16:37:51 -07:00
|
|
|
if c.cfg.Channel.IsInitiator() && feeProposal > c.maxFee {
|
|
|
|
return nil, false, fmt.Errorf("%w: %v > %v",
|
|
|
|
ErrProposalExeceedsMaxFee, feeProposal,
|
|
|
|
c.maxFee)
|
2022-04-26 12:44:18 -04:00
|
|
|
}
|
2017-11-23 00:54:28 -06:00
|
|
|
|
2023-01-19 19:43:47 -08:00
|
|
|
// With our new fee proposal calculated, we'll craft a
|
|
|
|
// new close signed signature to send to the other
|
|
|
|
// party so we can continue the fee negotiation
|
|
|
|
// process.
|
2017-11-23 00:54:28 -06:00
|
|
|
closeSigned, err := c.proposeCloseSigned(feeProposal)
|
|
|
|
if err != nil {
|
2023-01-19 19:43:47 -08:00
|
|
|
return nil, false, fmt.Errorf("unable to sign "+
|
|
|
|
"new co op close offer: %w", err)
|
2017-11-23 00:54:28 -06:00
|
|
|
}
|
|
|
|
|
2023-01-19 19:43:47 -08:00
|
|
|
// If the compromise fee doesn't match what the peer
|
|
|
|
// proposed, then we'll return this latest close signed
|
|
|
|
// message so we can continue negotiation.
|
2017-11-23 00:54:28 -06:00
|
|
|
if feeProposal != remoteProposedFee {
|
2020-06-16 20:33:06 -04:00
|
|
|
chancloserLog.Debugf("ChannelPoint(%v): close tx fee "+
|
2020-06-16 20:13:28 -04:00
|
|
|
"disagreement, continuing negotiation", c.chanPoint)
|
2017-11-23 00:54:28 -06:00
|
|
|
return []lnwire.Message{closeSigned}, false, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-16 20:33:06 -04:00
|
|
|
chancloserLog.Infof("ChannelPoint(%v) fee of %v accepted, ending "+
|
2017-11-23 00:54:28 -06:00
|
|
|
"negotiation", c.chanPoint, remoteProposedFee)
|
|
|
|
|
2023-01-19 19:43:47 -08:00
|
|
|
// Otherwise, we've agreed on a fee for the closing
|
|
|
|
// transaction! We'll craft the final closing transaction so we
|
|
|
|
// can broadcast it to the network.
|
|
|
|
var (
|
|
|
|
localSig, remoteSig input.Signature
|
|
|
|
closeOpts []lnwallet.ChanCloseOpt
|
|
|
|
err error
|
|
|
|
)
|
|
|
|
matchingSig := c.priorFeeOffers[remoteProposedFee]
|
|
|
|
if c.cfg.Channel.ChanType().IsTaproot() {
|
|
|
|
muSession := c.cfg.MusigSession
|
2023-07-23 17:35:20 +02:00
|
|
|
localSig, remoteSig, closeOpts, err = muSession.CombineClosingOpts( //nolint:lll
|
2023-01-19 19:43:47 -08:00
|
|
|
*matchingSig.PartialSig,
|
|
|
|
*closeSignedMsg.PartialSig,
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return nil, false, err
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
localSig, err = matchingSig.Signature.ToSignature()
|
|
|
|
if err != nil {
|
|
|
|
return nil, false, err
|
|
|
|
}
|
|
|
|
remoteSig, err = closeSignedMsg.Signature.ToSignature()
|
|
|
|
if err != nil {
|
|
|
|
return nil, false, err
|
|
|
|
}
|
2020-04-05 17:07:01 -07:00
|
|
|
}
|
2018-01-30 20:30:00 -08:00
|
|
|
|
2020-06-11 15:25:05 -04:00
|
|
|
closeTx, _, err := c.cfg.Channel.CompleteCooperativeClose(
|
2023-01-19 19:43:47 -08:00
|
|
|
localSig, remoteSig, c.localDeliveryScript,
|
|
|
|
c.remoteDeliveryScript, remoteProposedFee, closeOpts...,
|
2017-11-23 00:54:28 -06:00
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return nil, false, err
|
|
|
|
}
|
|
|
|
c.closingTx = closeTx
|
|
|
|
|
2023-01-19 19:43:47 -08:00
|
|
|
// Before publishing the closing tx, we persist it to the
|
|
|
|
// database, such that it can be republished if something goes
|
|
|
|
// wrong.
|
|
|
|
err = c.cfg.Channel.MarkCoopBroadcasted(
|
|
|
|
closeTx, c.locallyInitiated,
|
|
|
|
)
|
2019-09-06 13:14:40 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, false, err
|
|
|
|
}
|
|
|
|
|
2023-01-19 19:43:47 -08:00
|
|
|
// With the closing transaction crafted, we'll now broadcast it
|
|
|
|
// to the network.
|
2020-06-16 20:33:06 -04:00
|
|
|
chancloserLog.Infof("Broadcasting cooperative close tx: %v",
|
2017-11-23 00:54:28 -06:00
|
|
|
newLogClosure(func() string {
|
|
|
|
return spew.Sdump(closeTx)
|
2020-06-16 20:13:28 -04:00
|
|
|
}),
|
|
|
|
)
|
2020-07-29 09:27:22 +02:00
|
|
|
|
|
|
|
// Create a close channel label.
|
|
|
|
chanID := c.cfg.Channel.ShortChanID()
|
|
|
|
closeLabel := labels.MakeLabel(
|
|
|
|
labels.LabelTypeChannelClose, &chanID,
|
|
|
|
)
|
|
|
|
|
|
|
|
if err := c.cfg.BroadcastTx(closeTx, closeLabel); err != nil {
|
2018-05-15 15:44:33 +02:00
|
|
|
return nil, false, err
|
|
|
|
}
|
2017-11-23 00:54:28 -06:00
|
|
|
|
2023-01-19 19:43:47 -08:00
|
|
|
// Finally, we'll transition to the closeFinished state, and
|
|
|
|
// also return the final close signed message we sent.
|
|
|
|
// Additionally, we return true for the second argument to
|
|
|
|
// indicate we're finished with the channel closing
|
|
|
|
// negotiation.
|
2018-05-15 15:44:33 +02:00
|
|
|
c.state = closeFinished
|
2017-11-23 00:54:28 -06:00
|
|
|
matchingOffer := c.priorFeeOffers[remoteProposedFee]
|
|
|
|
return []lnwire.Message{matchingOffer}, true, nil
|
|
|
|
|
2020-06-16 20:13:28 -04:00
|
|
|
// If we received a message while in the closeFinished state, then this
|
|
|
|
// should only be the remote party echoing the last ClosingSigned message
|
|
|
|
// that we agreed on.
|
2017-11-23 00:54:28 -06:00
|
|
|
case closeFinished:
|
|
|
|
if _, ok := msg.(*lnwire.ClosingSigned); !ok {
|
2020-06-16 20:13:28 -04:00
|
|
|
return nil, false, fmt.Errorf("expected lnwire.ClosingSigned, "+
|
|
|
|
"instead have %v", spew.Sdump(msg))
|
2017-11-23 00:54:28 -06:00
|
|
|
}
|
|
|
|
|
2020-06-16 20:13:28 -04:00
|
|
|
// There's no more to do as both sides should have already broadcast
|
|
|
|
// the closing transaction at this state.
|
2017-11-23 00:54:28 -06:00
|
|
|
return nil, true, nil
|
|
|
|
|
|
|
|
// Otherwise, we're in an unknown state, and can't proceed.
|
|
|
|
default:
|
|
|
|
return nil, false, ErrInvalidState
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// proposeCloseSigned attempts to propose a new signature for the closing
|
2020-06-16 20:13:28 -04:00
|
|
|
// transaction for a channel based on the prior fee negotiations and our current
|
|
|
|
// compromise fee.
|
2020-06-16 20:33:06 -04:00
|
|
|
func (c *ChanCloser) proposeCloseSigned(fee btcutil.Amount) (*lnwire.ClosingSigned, error) {
|
2023-01-19 19:43:47 -08:00
|
|
|
var (
|
|
|
|
closeOpts []lnwallet.ChanCloseOpt
|
|
|
|
err error
|
|
|
|
)
|
|
|
|
|
|
|
|
// If this is a taproot channel, then we'll include the musig session
|
|
|
|
// generated for the next co-op close negotiation round.
|
|
|
|
if c.cfg.Channel.ChanType().IsTaproot() {
|
|
|
|
closeOpts, err = c.cfg.MusigSession.ProposalClosingOpts()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-11 15:25:05 -04:00
|
|
|
rawSig, _, _, err := c.cfg.Channel.CreateCloseProposal(
|
2017-11-23 00:54:28 -06:00
|
|
|
fee, c.localDeliveryScript, c.remoteDeliveryScript,
|
2023-01-19 19:43:47 -08:00
|
|
|
closeOpts...,
|
2017-11-23 00:54:28 -06:00
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2023-01-16 19:33:21 -08:00
|
|
|
// We'll note our last signature and proposed fee so when the remote
|
|
|
|
// party responds we'll be able to decide if we've agreed on fees or
|
|
|
|
// not.
|
2023-01-19 19:43:47 -08:00
|
|
|
var (
|
|
|
|
parsedSig lnwire.Sig
|
|
|
|
partialSig *lnwire.PartialSigWithNonce
|
|
|
|
)
|
|
|
|
if c.cfg.Channel.ChanType().IsTaproot() {
|
|
|
|
musig, ok := rawSig.(*lnwallet.MusigPartialSig)
|
|
|
|
if !ok {
|
|
|
|
return nil, fmt.Errorf("expected MusigPartialSig, "+
|
|
|
|
"got %T", rawSig)
|
|
|
|
}
|
2023-01-16 19:33:21 -08:00
|
|
|
|
2023-01-19 19:43:47 -08:00
|
|
|
partialSig = musig.ToWireSig()
|
|
|
|
} else {
|
|
|
|
parsedSig, err = lnwire.NewSigFromSignature(rawSig)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2017-11-23 00:54:28 -06:00
|
|
|
}
|
|
|
|
|
2023-01-19 19:43:47 -08:00
|
|
|
c.lastFeeProposal = fee
|
|
|
|
|
2023-01-16 19:33:21 -08:00
|
|
|
chancloserLog.Infof("ChannelPoint(%v): proposing fee of %v sat to "+
|
|
|
|
"close chan", c.chanPoint, int64(fee))
|
2017-11-23 00:54:28 -06:00
|
|
|
|
2023-01-19 19:43:47 -08:00
|
|
|
// We'll assemble a ClosingSigned message using this information and
|
|
|
|
// return it to the caller so we can kick off the final stage of the
|
|
|
|
// channel closure process.
|
2017-11-23 00:54:28 -06:00
|
|
|
closeSignedMsg := lnwire.NewClosingSigned(c.cid, fee, parsedSig)
|
|
|
|
|
2023-01-19 19:43:47 -08:00
|
|
|
// For musig2 channels, the main sig is blank, and instead we'll send
|
|
|
|
// over a partial signature which'll be combine donce our offer is
|
|
|
|
// accepted.
|
|
|
|
if partialSig != nil {
|
|
|
|
closeSignedMsg.PartialSig = &partialSig.PartialSig
|
|
|
|
}
|
|
|
|
|
2017-11-23 00:54:28 -06:00
|
|
|
// We'll also save this close signed, in the case that the remote party
|
|
|
|
// accepts our offer. This way, we don't have to re-sign.
|
|
|
|
c.priorFeeOffers[fee] = closeSignedMsg
|
|
|
|
|
|
|
|
return closeSignedMsg, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// feeInAcceptableRange returns true if the passed remote fee is deemed to be
|
|
|
|
// in an "acceptable" range to our local fee. This is an attempt at a
|
|
|
|
// compromise and to ensure that the fee negotiation has a stopping point. We
|
|
|
|
// consider their fee acceptable if it's within 30% of our fee.
|
|
|
|
func feeInAcceptableRange(localFee, remoteFee btcutil.Amount) bool {
|
2020-06-16 20:13:28 -04:00
|
|
|
// If our offer is lower than theirs, then we'll accept their offer if it's
|
|
|
|
// no more than 30% *greater* than our current offer.
|
2017-11-23 00:54:28 -06:00
|
|
|
if localFee < remoteFee {
|
|
|
|
acceptableRange := localFee + ((localFee * 3) / 10)
|
|
|
|
return remoteFee <= acceptableRange
|
|
|
|
}
|
|
|
|
|
2020-06-16 20:13:28 -04:00
|
|
|
// If our offer is greater than theirs, then we'll accept their offer if
|
|
|
|
// it's no more than 30% *less* than our current offer.
|
2017-11-23 00:54:28 -06:00
|
|
|
acceptableRange := localFee - ((localFee * 3) / 10)
|
|
|
|
return remoteFee >= acceptableRange
|
|
|
|
}
|
|
|
|
|
2020-06-16 20:13:28 -04:00
|
|
|
// ratchetFee is our step function used to inch our fee closer to something
|
|
|
|
// that both sides can agree on. If up is true, then we'll attempt to increase
|
|
|
|
// our offered fee. Otherwise, if up is false, then we'll attempt to decrease
|
|
|
|
// our offered fee.
|
|
|
|
func ratchetFee(fee btcutil.Amount, up bool) btcutil.Amount {
|
|
|
|
// If we need to ratchet up, then we'll increase our fee by 10%.
|
2017-11-23 00:54:28 -06:00
|
|
|
if up {
|
|
|
|
return fee + ((fee * 1) / 10)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Otherwise, we'll *decrease* our fee by 10%.
|
|
|
|
return fee - ((fee * 1) / 10)
|
|
|
|
}
|
|
|
|
|
|
|
|
// calcCompromiseFee performs the current fee negotiation algorithm, taking
|
|
|
|
// into consideration our ideal fee based on current fee environment, the fee
|
|
|
|
// we last proposed (if any), and the fee proposed by the peer.
|
2020-06-16 20:13:28 -04:00
|
|
|
func calcCompromiseFee(chanPoint wire.OutPoint, ourIdealFee, lastSentFee,
|
|
|
|
remoteFee btcutil.Amount) btcutil.Amount {
|
2017-11-23 00:54:28 -06:00
|
|
|
|
|
|
|
// TODO(roasbeef): take in number of rounds as well?
|
|
|
|
|
2020-06-16 20:33:06 -04:00
|
|
|
chancloserLog.Infof("ChannelPoint(%v): computing fee compromise, ideal="+
|
2020-06-16 20:13:28 -04:00
|
|
|
"%v, last_sent=%v, remote_offer=%v", chanPoint, int64(ourIdealFee),
|
2017-11-23 00:54:28 -06:00
|
|
|
int64(lastSentFee), int64(remoteFee))
|
|
|
|
|
2020-06-16 20:13:28 -04:00
|
|
|
// Otherwise, we'll need to attempt to make a fee compromise if this is the
|
|
|
|
// second round, and neither side has agreed on fees.
|
2017-11-23 00:54:28 -06:00
|
|
|
switch {
|
2020-06-16 20:13:28 -04:00
|
|
|
// If their proposed fee is identical to our ideal fee, then we'll go with
|
|
|
|
// that as we can short circuit the fee negotiation. Similarly, if we
|
|
|
|
// haven't sent an offer yet, we'll default to our ideal fee.
|
2017-11-23 00:54:28 -06:00
|
|
|
case ourIdealFee == remoteFee || lastSentFee == 0:
|
|
|
|
return ourIdealFee
|
|
|
|
|
|
|
|
// If the last fee we sent, is equal to the fee the remote party is
|
2020-06-16 20:13:28 -04:00
|
|
|
// offering, then we can simply return this fee as the negotiation is over.
|
2017-11-23 00:54:28 -06:00
|
|
|
case remoteFee == lastSentFee:
|
|
|
|
return lastSentFee
|
|
|
|
|
|
|
|
// If the fee the remote party is offering is less than the last one we
|
2020-06-16 20:13:28 -04:00
|
|
|
// sent, then we'll need to ratchet down in order to move our offer closer
|
|
|
|
// to theirs.
|
2017-11-23 00:54:28 -06:00
|
|
|
case remoteFee < lastSentFee:
|
2020-06-16 20:13:28 -04:00
|
|
|
// If the fee is lower, but still acceptable, then we'll just return
|
|
|
|
// this fee and end the negotiation.
|
2017-11-23 00:54:28 -06:00
|
|
|
if feeInAcceptableRange(lastSentFee, remoteFee) {
|
2020-06-16 20:33:06 -04:00
|
|
|
chancloserLog.Infof("ChannelPoint(%v): proposed remote fee is "+
|
2020-06-16 20:13:28 -04:00
|
|
|
"close enough, capitulating", chanPoint)
|
2017-11-23 00:54:28 -06:00
|
|
|
return remoteFee
|
|
|
|
}
|
|
|
|
|
2020-06-16 20:13:28 -04:00
|
|
|
// Otherwise, we'll ratchet the fee *down* using our current algorithm.
|
|
|
|
return ratchetFee(lastSentFee, false)
|
2017-11-23 00:54:28 -06:00
|
|
|
|
2020-06-16 20:13:28 -04:00
|
|
|
// If the fee the remote party is offering is greater than the last one we
|
|
|
|
// sent, then we'll ratchet up in order to ensure we terminate eventually.
|
2017-11-23 00:54:28 -06:00
|
|
|
case remoteFee > lastSentFee:
|
2020-06-16 20:13:28 -04:00
|
|
|
// If the fee is greater, but still acceptable, then we'll just return
|
|
|
|
// this fee in order to put an end to the negotiation.
|
2017-11-23 00:54:28 -06:00
|
|
|
if feeInAcceptableRange(lastSentFee, remoteFee) {
|
2020-06-16 20:33:06 -04:00
|
|
|
chancloserLog.Infof("ChannelPoint(%v): proposed remote fee is "+
|
2020-06-16 20:13:28 -04:00
|
|
|
"close enough, capitulating", chanPoint)
|
2017-11-23 00:54:28 -06:00
|
|
|
return remoteFee
|
|
|
|
}
|
|
|
|
|
2020-06-16 20:33:06 -04:00
|
|
|
// Otherwise, we'll ratchet the fee up using our current algorithm.
|
2020-06-16 20:13:28 -04:00
|
|
|
return ratchetFee(lastSentFee, true)
|
2017-11-23 00:54:28 -06:00
|
|
|
|
|
|
|
default:
|
|
|
|
// TODO(roasbeef): fail if their fee isn't in expected range
|
|
|
|
return remoteFee
|
|
|
|
}
|
|
|
|
}
|
2020-11-09 09:34:51 +02:00
|
|
|
|
|
|
|
// ParseUpfrontShutdownAddress attempts to parse an upfront shutdown address.
|
|
|
|
// If the address is empty, it returns nil. If it successfully decoded the
|
|
|
|
// address, it returns a script that pays out to the address.
|
|
|
|
func ParseUpfrontShutdownAddress(address string,
|
|
|
|
params *chaincfg.Params) (lnwire.DeliveryAddress, error) {
|
|
|
|
|
|
|
|
if len(address) == 0 {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
addr, err := btcutil.DecodeAddress(
|
|
|
|
address, params,
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("invalid address: %v", err)
|
|
|
|
}
|
|
|
|
|
2022-12-23 03:26:55 -08:00
|
|
|
if !addr.IsForNet(params) {
|
|
|
|
return nil, fmt.Errorf("invalid address: %v is not a %s "+
|
|
|
|
"address", addr, params.Name)
|
|
|
|
}
|
|
|
|
|
2020-11-09 09:34:51 +02:00
|
|
|
return txscript.PayToAddrScript(addr)
|
|
|
|
}
|