2020-06-16 20:33:06 -04:00
|
|
|
package chancloser
|
2019-12-03 11:38:29 +02:00
|
|
|
|
|
|
|
import (
|
2022-06-10 11:17:20 -07:00
|
|
|
"bytes"
|
2022-07-26 16:39:36 -07:00
|
|
|
"fmt"
|
2019-12-03 11:38:29 +02:00
|
|
|
"testing"
|
2023-01-19 19:43:47 -08:00
|
|
|
"time"
|
2019-12-03 11:38:29 +02:00
|
|
|
|
2023-01-19 19:43:47 -08:00
|
|
|
"github.com/btcsuite/btcd/btcec/v2"
|
|
|
|
"github.com/btcsuite/btcd/btcec/v2/schnorr/musig2"
|
2022-07-26 16:39:36 -07:00
|
|
|
"github.com/btcsuite/btcd/btcutil"
|
2022-06-10 11:17:20 -07:00
|
|
|
"github.com/btcsuite/btcd/chaincfg"
|
2022-07-26 16:39:36 -07:00
|
|
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
2022-06-10 11:17:20 -07:00
|
|
|
"github.com/btcsuite/btcd/txscript"
|
2022-07-26 16:39:36 -07:00
|
|
|
"github.com/btcsuite/btcd/wire"
|
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"
|
2022-07-26 16:39:36 -07:00
|
|
|
"github.com/lightningnetwork/lnd/input"
|
2023-01-19 19:43:47 -08:00
|
|
|
"github.com/lightningnetwork/lnd/keychain"
|
|
|
|
"github.com/lightningnetwork/lnd/lnutils"
|
|
|
|
"github.com/lightningnetwork/lnd/lnwallet"
|
2022-07-26 16:39:36 -07:00
|
|
|
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
2019-12-03 11:38:29 +02:00
|
|
|
"github.com/lightningnetwork/lnd/lnwire"
|
2022-05-05 20:11:50 +00:00
|
|
|
"github.com/stretchr/testify/require"
|
2019-12-03 11:38:29 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
// TestMaybeMatchScript tests that the maybeMatchScript errors appropriately
|
|
|
|
// when an upfront shutdown script is set and the script provided does not
|
|
|
|
// match, and does not error in any other case.
|
|
|
|
func TestMaybeMatchScript(t *testing.T) {
|
2022-07-26 16:39:36 -07:00
|
|
|
t.Parallel()
|
|
|
|
|
2022-06-10 11:17:20 -07:00
|
|
|
pubHash := bytes.Repeat([]byte{0x0}, 20)
|
|
|
|
scriptHash := bytes.Repeat([]byte{0x0}, 32)
|
|
|
|
|
|
|
|
p2wkh, err := txscript.NewScriptBuilder().AddOp(txscript.OP_0).
|
|
|
|
AddData(pubHash).Script()
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
p2wsh, err := txscript.NewScriptBuilder().AddOp(txscript.OP_0).
|
|
|
|
AddData(scriptHash).Script()
|
|
|
|
require.NoError(t, err)
|
2019-12-03 11:38:29 +02:00
|
|
|
|
2022-06-10 11:17:20 -07:00
|
|
|
p2tr, err := txscript.NewScriptBuilder().AddOp(txscript.OP_1).
|
|
|
|
AddData(scriptHash).Script()
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
p2OtherV1, err := txscript.NewScriptBuilder().AddOp(txscript.OP_1).
|
|
|
|
AddData(pubHash).Script()
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
invalidFork, err := txscript.NewScriptBuilder().AddOp(txscript.OP_NOP).
|
|
|
|
AddData(scriptHash).Script()
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
type testCase struct {
|
2019-12-03 11:38:29 +02:00
|
|
|
name string
|
|
|
|
shutdownScript lnwire.DeliveryAddress
|
|
|
|
upfrontScript lnwire.DeliveryAddress
|
|
|
|
expectedErr error
|
2022-06-10 11:17:20 -07:00
|
|
|
}
|
|
|
|
tests := []testCase{
|
2019-12-03 11:38:29 +02:00
|
|
|
{
|
|
|
|
name: "no upfront shutdown set, script ok",
|
2022-06-10 11:17:20 -07:00
|
|
|
shutdownScript: p2wkh,
|
2019-12-03 11:38:29 +02:00
|
|
|
upfrontScript: []byte{},
|
|
|
|
expectedErr: nil,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "upfront shutdown set, script ok",
|
2022-06-10 11:17:20 -07:00
|
|
|
shutdownScript: p2wkh,
|
|
|
|
upfrontScript: p2wkh,
|
2019-12-03 11:38:29 +02:00
|
|
|
expectedErr: nil,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "upfront shutdown set, script not ok",
|
2022-06-10 11:17:20 -07:00
|
|
|
shutdownScript: p2wkh,
|
|
|
|
upfrontScript: p2wsh,
|
2020-06-11 15:25:05 -04:00
|
|
|
expectedErr: ErrUpfrontShutdownScriptMismatch,
|
2019-12-03 11:38:29 +02:00
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "nil shutdown and empty upfront",
|
|
|
|
shutdownScript: nil,
|
|
|
|
upfrontScript: []byte{},
|
|
|
|
expectedErr: nil,
|
|
|
|
},
|
2022-06-10 11:17:20 -07:00
|
|
|
{
|
|
|
|
name: "p2tr is ok",
|
|
|
|
shutdownScript: p2tr,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "segwit v1 is ok",
|
|
|
|
shutdownScript: p2OtherV1,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "invalid script not allowed",
|
|
|
|
shutdownScript: invalidFork,
|
|
|
|
expectedErr: ErrInvalidShutdownScript,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
// All future segwit softforks should also be ok.
|
|
|
|
futureForks := []byte{
|
|
|
|
txscript.OP_1, txscript.OP_2, txscript.OP_3, txscript.OP_4,
|
|
|
|
txscript.OP_5, txscript.OP_6, txscript.OP_7, txscript.OP_8,
|
|
|
|
txscript.OP_9, txscript.OP_10, txscript.OP_11, txscript.OP_12,
|
|
|
|
txscript.OP_13, txscript.OP_14, txscript.OP_15, txscript.OP_16,
|
|
|
|
}
|
|
|
|
for _, witnessVersion := range futureForks {
|
|
|
|
p2FutureFork, err := txscript.NewScriptBuilder().AddOp(witnessVersion).
|
|
|
|
AddData(scriptHash).Script()
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
opString, err := txscript.DisasmString([]byte{witnessVersion})
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
tests = append(tests, testCase{
|
|
|
|
name: fmt.Sprintf("witness_version=%v", opString),
|
|
|
|
shutdownScript: p2FutureFork,
|
|
|
|
})
|
2019-12-03 11:38:29 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
for _, test := range tests {
|
|
|
|
test := test
|
|
|
|
|
|
|
|
t.Run(test.name, func(t *testing.T) {
|
2022-07-26 16:39:36 -07:00
|
|
|
t.Parallel()
|
|
|
|
|
2022-06-10 11:17:20 -07:00
|
|
|
err := validateShutdownScript(
|
2019-12-03 11:38:29 +02:00
|
|
|
func() error { return nil }, test.upfrontScript,
|
2022-06-10 11:17:20 -07:00
|
|
|
test.shutdownScript, &chaincfg.SimNetParams,
|
2019-12-03 11:38:29 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
if err != test.expectedErr {
|
|
|
|
t.Fatalf("Error: %v, expected error: %v", err, test.expectedErr)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
2022-07-26 16:39:36 -07:00
|
|
|
|
|
|
|
type mockChannel 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
|
|
|
chanPoint wire.OutPoint
|
|
|
|
initiator bool
|
|
|
|
scid lnwire.ShortChannelID
|
2023-01-19 19:43:47 -08:00
|
|
|
chanType channeldb.ChannelType
|
|
|
|
localKey keychain.KeyDescriptor
|
|
|
|
remoteKey keychain.KeyDescriptor
|
2022-07-26 16:39:36 -07:00
|
|
|
}
|
|
|
|
|
2024-01-29 15:59:51 -05:00
|
|
|
func (m *mockChannel) ChannelPoint() wire.OutPoint {
|
|
|
|
return m.chanPoint
|
2022-07-26 16:39:36 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
func (m *mockChannel) MarkCoopBroadcasted(*wire.MsgTx, bool) error {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-02-06 15:56:27 +02:00
|
|
|
func (m *mockChannel) MarkShutdownSent(*channeldb.ShutdownInfo) error {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-07-26 16:39:36 -07:00
|
|
|
func (m *mockChannel) IsInitiator() bool {
|
|
|
|
return m.initiator
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *mockChannel) ShortChanID() lnwire.ShortChannelID {
|
|
|
|
return m.scid
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *mockChannel) AbsoluteThawHeight() (uint32, error) {
|
|
|
|
return 0, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *mockChannel) RemoteUpfrontShutdownScript() lnwire.DeliveryAddress {
|
|
|
|
return lnwire.DeliveryAddress{}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *mockChannel) CreateCloseProposal(fee btcutil.Amount,
|
2023-01-19 19:43:47 -08:00
|
|
|
localScript, remoteScript []byte, _ ...lnwallet.ChanCloseOpt,
|
2022-07-26 16:39:36 -07:00
|
|
|
) (input.Signature, *chainhash.Hash, btcutil.Amount, error) {
|
|
|
|
|
2023-01-19 19:43:47 -08:00
|
|
|
if m.chanType.IsTaproot() {
|
|
|
|
return lnwallet.NewMusigPartialSig(
|
|
|
|
&musig2.PartialSignature{
|
|
|
|
S: new(btcec.ModNScalar),
|
|
|
|
R: new(btcec.PublicKey),
|
|
|
|
},
|
|
|
|
lnwire.Musig2Nonce{}, lnwire.Musig2Nonce{}, nil,
|
|
|
|
), nil, 0, nil
|
|
|
|
}
|
|
|
|
|
2022-07-26 16:39:36 -07:00
|
|
|
return nil, nil, 0, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *mockChannel) CompleteCooperativeClose(localSig,
|
|
|
|
remoteSig input.Signature, localScript, remoteScript []byte,
|
2023-01-19 19:43:47 -08:00
|
|
|
proposedFee btcutil.Amount,
|
|
|
|
_ ...lnwallet.ChanCloseOpt) (*wire.MsgTx, btcutil.Amount, error) {
|
2022-07-26 16:39:36 -07:00
|
|
|
|
2023-12-01 17:18:01 -08:00
|
|
|
return &wire.MsgTx{}, 0, nil
|
2022-07-26 16:39:36 -07: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
|
|
|
func (m *mockChannel) LocalBalanceDust() bool {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *mockChannel) RemoteBalanceDust() bool {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2023-01-19 19:43:47 -08:00
|
|
|
func (m *mockChannel) ChanType() channeldb.ChannelType {
|
|
|
|
return m.chanType
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *mockChannel) FundingTxOut() *wire.TxOut {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-07-23 17:35:20 +02:00
|
|
|
func (m *mockChannel) MultiSigKeys() (
|
|
|
|
keychain.KeyDescriptor, keychain.KeyDescriptor) {
|
|
|
|
|
2023-01-19 19:43:47 -08:00
|
|
|
return m.localKey, m.remoteKey
|
|
|
|
}
|
|
|
|
|
|
|
|
func newMockTaprootChan(t *testing.T, initiator bool) *mockChannel {
|
|
|
|
taprootBits := channeldb.SimpleTaprootFeatureBit |
|
|
|
|
channeldb.AnchorOutputsBit |
|
|
|
|
channeldb.ZeroHtlcTxFeeBit |
|
|
|
|
channeldb.SingleFunderTweaklessBit
|
|
|
|
|
|
|
|
localKey, err := btcec.NewPrivateKey()
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
remoteKey, err := btcec.NewPrivateKey()
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
return &mockChannel{
|
|
|
|
chanPoint: wire.OutPoint{
|
|
|
|
Hash: chainhash.Hash{},
|
|
|
|
Index: 0,
|
|
|
|
},
|
|
|
|
initiator: initiator,
|
|
|
|
chanType: taprootBits,
|
|
|
|
localKey: keychain.KeyDescriptor{
|
|
|
|
PubKey: localKey.PubKey(),
|
|
|
|
},
|
|
|
|
remoteKey: keychain.KeyDescriptor{
|
|
|
|
PubKey: remoteKey.PubKey(),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type mockMusigSession struct {
|
|
|
|
}
|
|
|
|
|
|
|
|
func newMockMusigSession() *mockMusigSession {
|
|
|
|
return &mockMusigSession{}
|
|
|
|
}
|
|
|
|
|
2023-07-23 17:35:20 +02:00
|
|
|
func (m *mockMusigSession) ProposalClosingOpts() ([]lnwallet.ChanCloseOpt,
|
|
|
|
error) {
|
|
|
|
|
2023-01-19 19:43:47 -08:00
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *mockMusigSession) CombineClosingOpts(localSig,
|
|
|
|
remoteSig lnwire.PartialSig,
|
|
|
|
) (input.Signature, input.Signature, []lnwallet.ChanCloseOpt, error) {
|
|
|
|
|
|
|
|
return &lnwallet.MusigPartialSig{}, &lnwallet.MusigPartialSig{}, nil,
|
|
|
|
nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *mockMusigSession) ClosingNonce() (*musig2.Nonces, error) {
|
|
|
|
return &musig2.Nonces{}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *mockMusigSession) InitRemoteNonce(nonce *musig2.Nonces) {
|
|
|
|
}
|
|
|
|
|
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
|
|
|
type mockCoopFeeEstimator struct {
|
|
|
|
targetFee btcutil.Amount
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *mockCoopFeeEstimator) EstimateFee(chanType channeldb.ChannelType,
|
|
|
|
localTxOut, remoteTxOut *wire.TxOut,
|
|
|
|
idealFeeRate chainfee.SatPerKWeight) btcutil.Amount {
|
|
|
|
|
|
|
|
return m.targetFee
|
|
|
|
}
|
|
|
|
|
2022-07-26 16:39:36 -07:00
|
|
|
// TestMaxFeeClamp tests that if a max fee is specified, then it's used instead
|
|
|
|
// of the default max fee multiplier.
|
|
|
|
func TestMaxFeeClamp(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
|
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
|
|
|
const (
|
|
|
|
absoluteFeeOneSatByte = 126
|
|
|
|
absoluteFeeTenSatByte = 1265
|
|
|
|
)
|
2022-07-26 16:39:36 -07:00
|
|
|
|
|
|
|
tests := []struct {
|
|
|
|
name string
|
|
|
|
|
|
|
|
idealFee chainfee.SatPerKWeight
|
|
|
|
inputMaxFee chainfee.SatPerKWeight
|
|
|
|
|
|
|
|
maxFee btcutil.Amount
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
// No max fee specified, we should see 3x the ideal fee.
|
|
|
|
name: "no max fee",
|
|
|
|
|
|
|
|
idealFee: chainfee.SatPerKWeight(253),
|
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
|
|
|
maxFee: absoluteFeeOneSatByte * defaultMaxFeeMultiplier,
|
|
|
|
},
|
|
|
|
{
|
2022-07-26 16:39:36 -07:00
|
|
|
// Max fee specified, this should be used in place.
|
|
|
|
name: "max fee clamp",
|
|
|
|
|
|
|
|
idealFee: chainfee.SatPerKWeight(253),
|
|
|
|
inputMaxFee: chainfee.SatPerKWeight(2530),
|
|
|
|
|
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
|
|
|
// We should get the resulting absolute fee based on a
|
|
|
|
// factor of 10 sat/byte (our new max fee).
|
|
|
|
maxFee: absoluteFeeTenSatByte,
|
2022-07-26 16:39:36 -07:00
|
|
|
},
|
|
|
|
}
|
|
|
|
for _, test := range tests {
|
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
|
|
|
test := test
|
|
|
|
|
2022-07-26 16:39:36 -07:00
|
|
|
t.Run(test.name, func(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
|
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
|
|
|
channel := mockChannel{}
|
2022-07-26 16:39:36 -07:00
|
|
|
|
|
|
|
chanCloser := NewChanCloser(
|
|
|
|
ChanCloseCfg{
|
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
|
|
|
Channel: &channel,
|
|
|
|
MaxFee: test.inputMaxFee,
|
|
|
|
FeeEstimator: &SimpleCoopFeeEstimator{},
|
2022-07-26 16:39:36 -07:00
|
|
|
}, nil, test.idealFee, 0, nil, false,
|
|
|
|
)
|
|
|
|
|
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
|
|
|
// We'll call initFeeBaseline early here since we need
|
|
|
|
// the populate these internal variables.
|
|
|
|
chanCloser.initFeeBaseline()
|
|
|
|
|
2022-07-26 16:39:36 -07:00
|
|
|
require.Equal(t, test.maxFee, chanCloser.maxFee)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// TestMaxFeeBailOut tests that once the negotiated fee rate rises above our
|
|
|
|
// maximum fee, we'll return an error and refuse to process a co-op close
|
|
|
|
// message.
|
|
|
|
func TestMaxFeeBailOut(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
const (
|
|
|
|
absoluteFee = btcutil.Amount(1000)
|
|
|
|
idealFee = chainfee.SatPerKWeight(253)
|
|
|
|
)
|
|
|
|
|
|
|
|
for _, isInitiator := range []bool{true, false} {
|
2022-10-06 17:42:06 -07:00
|
|
|
isInitiator := isInitiator
|
|
|
|
|
2022-07-26 16:39:36 -07:00
|
|
|
t.Run(fmt.Sprintf("initiator=%v", isInitiator), func(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
// First, we'll make our mock channel, and use that to
|
|
|
|
// instantiate our channel closer.
|
|
|
|
closeCfg := ChanCloseCfg{
|
|
|
|
Channel: &mockChannel{
|
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
|
|
|
initiator: isInitiator,
|
|
|
|
},
|
|
|
|
FeeEstimator: &mockCoopFeeEstimator{
|
|
|
|
targetFee: absoluteFee,
|
2022-07-26 16:39:36 -07:00
|
|
|
},
|
|
|
|
MaxFee: idealFee * 2,
|
|
|
|
}
|
|
|
|
chanCloser := NewChanCloser(
|
|
|
|
closeCfg, nil, idealFee, 0, nil, false,
|
|
|
|
)
|
|
|
|
|
|
|
|
// We'll now force the channel state into the
|
|
|
|
// closeFeeNegotiation state so we can skip straight to
|
|
|
|
// the juicy part. We'll also set our last fee sent so
|
|
|
|
// we'll attempt to actually "negotiate" here.
|
|
|
|
chanCloser.state = closeFeeNegotiation
|
|
|
|
chanCloser.lastFeeProposal = absoluteFee
|
|
|
|
|
|
|
|
// Next, we'll make a ClosingSigned message that
|
|
|
|
// proposes a fee that's above the specified max fee.
|
|
|
|
//
|
|
|
|
// NOTE: We use the absoluteFee here since our mock
|
|
|
|
// always returns this fee for the CalcFee method which
|
|
|
|
// is used to translate a fee rate
|
|
|
|
// into an absolute fee amount in sats.
|
|
|
|
closeMsg := &lnwire.ClosingSigned{
|
|
|
|
FeeSatoshis: absoluteFee * 2,
|
|
|
|
}
|
|
|
|
|
2023-12-01 17:18:01 -08:00
|
|
|
_, err := chanCloser.ReceiveClosingSigned(*closeMsg)
|
2022-07-26 16:39:36 -07:00
|
|
|
|
|
|
|
switch isInitiator {
|
|
|
|
// If we're the initiator, then we expect an error at
|
|
|
|
// this point.
|
|
|
|
case true:
|
2023-09-20 11:37:32 -04:00
|
|
|
require.ErrorIs(
|
|
|
|
t, err, ErrProposalExceedsMaxFee,
|
|
|
|
)
|
2022-07-26 16:39:36 -07:00
|
|
|
|
|
|
|
// Otherwise, we expect things to fail for some other
|
|
|
|
// reason (invalid sig, etc).
|
|
|
|
case false:
|
2023-09-20 11:37:32 -04:00
|
|
|
require.NotErrorIs(
|
|
|
|
t, err, ErrProposalExceedsMaxFee,
|
|
|
|
)
|
2022-07-26 16:39:36 -07:00
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
2022-12-23 03:26:55 -08:00
|
|
|
|
|
|
|
// TestParseUpfrontShutdownAddress tests the we are able to parse the upfront
|
|
|
|
// shutdown address properly.
|
|
|
|
func TestParseUpfrontShutdownAddress(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
var (
|
|
|
|
testnetAddress = "tb1qdfkmwwgdaa5dnezrlhtftvmj5qn2kwgp7n0z6r"
|
|
|
|
regtestAddress = "bcrt1q09crvvuj95x5nk64wsxf5n6ky0kr8358vpx4d8"
|
|
|
|
)
|
|
|
|
|
|
|
|
tests := []struct {
|
|
|
|
name string
|
|
|
|
address string
|
|
|
|
params chaincfg.Params
|
|
|
|
expectedErr string
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "invalid closing address",
|
|
|
|
address: "non-valid-address",
|
|
|
|
params: chaincfg.RegressionNetParams,
|
|
|
|
expectedErr: "invalid address",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "closing address from another net",
|
|
|
|
address: testnetAddress,
|
|
|
|
params: chaincfg.RegressionNetParams,
|
|
|
|
expectedErr: "not a regtest address",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "valid p2wkh closing address",
|
|
|
|
address: regtestAddress,
|
|
|
|
params: chaincfg.RegressionNetParams,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, tc := range tests {
|
|
|
|
tc := tc
|
|
|
|
|
|
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
_, err := ParseUpfrontShutdownAddress(
|
|
|
|
tc.address, &tc.params,
|
|
|
|
)
|
|
|
|
|
|
|
|
if tc.expectedErr != "" {
|
|
|
|
require.ErrorContains(t, err, tc.expectedErr)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
2023-01-19 19:43:47 -08:00
|
|
|
|
|
|
|
// TestTaprootFastClose tests that we are able to properly execute a fast close
|
|
|
|
// (skip negotiation) for taproot channels.
|
|
|
|
func TestTaprootFastClose(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
aliceChan := newMockTaprootChan(t, true)
|
|
|
|
bobChan := newMockTaprootChan(t, false)
|
|
|
|
|
|
|
|
broadcastSignal := make(chan struct{}, 2)
|
|
|
|
|
|
|
|
idealFee := chainfee.SatPerKWeight(506)
|
|
|
|
|
|
|
|
// Next, we'll make a channel for Alice and Bob, with Alice being the
|
|
|
|
// initiator.
|
|
|
|
aliceCloser := NewChanCloser(
|
|
|
|
ChanCloseCfg{
|
|
|
|
Channel: aliceChan,
|
|
|
|
MusigSession: newMockMusigSession(),
|
|
|
|
BroadcastTx: func(_ *wire.MsgTx, _ string) error {
|
|
|
|
broadcastSignal <- struct{}{}
|
|
|
|
return nil
|
|
|
|
},
|
|
|
|
MaxFee: chainfee.SatPerKWeight(1000),
|
|
|
|
FeeEstimator: &SimpleCoopFeeEstimator{},
|
|
|
|
DisableChannel: func(wire.OutPoint) error {
|
|
|
|
return nil
|
|
|
|
},
|
|
|
|
}, nil, idealFee, 0, nil, true,
|
|
|
|
)
|
|
|
|
aliceCloser.initFeeBaseline()
|
|
|
|
|
|
|
|
bobCloser := NewChanCloser(
|
|
|
|
ChanCloseCfg{
|
|
|
|
Channel: bobChan,
|
|
|
|
MusigSession: newMockMusigSession(),
|
|
|
|
MaxFee: chainfee.SatPerKWeight(1000),
|
|
|
|
BroadcastTx: func(_ *wire.MsgTx, _ string) error {
|
|
|
|
broadcastSignal <- struct{}{}
|
|
|
|
return nil
|
|
|
|
},
|
|
|
|
FeeEstimator: &SimpleCoopFeeEstimator{},
|
|
|
|
DisableChannel: func(wire.OutPoint) error {
|
|
|
|
return nil
|
|
|
|
},
|
|
|
|
}, nil, idealFee, 0, nil, false,
|
|
|
|
)
|
|
|
|
bobCloser.initFeeBaseline()
|
|
|
|
|
|
|
|
// With our set up complete, we'll now initialize the shutdown
|
|
|
|
// procedure kicked off by Alice.
|
|
|
|
msg, err := aliceCloser.ShutdownChan()
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.NotNil(t, msg)
|
|
|
|
|
|
|
|
// Bob will then process this message. As he's the responder, he should
|
|
|
|
// only send the shutdown message back to Alice.
|
2023-12-01 17:18:01 -08:00
|
|
|
oShutdown, err := bobCloser.ReceiveShutdown(*msg)
|
2023-01-19 19:43:47 -08:00
|
|
|
require.NoError(t, err)
|
2023-12-01 17:18:01 -08:00
|
|
|
oClosingSigned, err := bobCloser.BeginNegotiation()
|
|
|
|
require.NoError(t, err)
|
|
|
|
tx, _ := bobCloser.ClosingTx()
|
|
|
|
require.Nil(t, tx)
|
|
|
|
require.True(t, oShutdown.IsSome())
|
|
|
|
require.True(t, oClosingSigned.IsNone())
|
|
|
|
|
2023-01-19 19:43:47 -08:00
|
|
|
// Alice should process the shutdown message, and create a closing
|
|
|
|
// signed of her own.
|
multi: upgrade new taproot TLVs to use tlv.OptionalRecordT
In this commit, we update new Taproot related TLVs (nonces, partial sig,
sig with nonce, etc). Along the way we were able to get rid of some
boiler plate, but most importantly, we're able to better protect against
API misuse (using a nonce that isn't initialized, etc) with the new
options API. In some areas this introduces a bit of extra boiler plate,
and where applicable I used some new helper functions to help cut down
on the noise.
Note to reviewers: this is done as a single commit, as changing the API
breaks all callers, so if we want things to compile it needs to be in a
wumbo commit.
2024-02-23 18:04:51 -08:00
|
|
|
oShutdown, err = aliceCloser.ReceiveShutdown(oShutdown.UnwrapOrFail(t))
|
2023-01-19 19:43:47 -08:00
|
|
|
require.NoError(t, err)
|
2023-12-01 17:18:01 -08:00
|
|
|
oClosingSigned, err = aliceCloser.BeginNegotiation()
|
|
|
|
require.NoError(t, err)
|
|
|
|
tx, _ = aliceCloser.ClosingTx()
|
|
|
|
require.Nil(t, tx)
|
|
|
|
require.True(t, oShutdown.IsNone())
|
|
|
|
require.True(t, oClosingSigned.IsSome())
|
|
|
|
|
multi: upgrade new taproot TLVs to use tlv.OptionalRecordT
In this commit, we update new Taproot related TLVs (nonces, partial sig,
sig with nonce, etc). Along the way we were able to get rid of some
boiler plate, but most importantly, we're able to better protect against
API misuse (using a nonce that isn't initialized, etc) with the new
options API. In some areas this introduces a bit of extra boiler plate,
and where applicable I used some new helper functions to help cut down
on the noise.
Note to reviewers: this is done as a single commit, as changing the API
breaks all callers, so if we want things to compile it needs to be in a
wumbo commit.
2024-02-23 18:04:51 -08:00
|
|
|
aliceClosingSigned := oClosingSigned.UnwrapOrFail(t)
|
2023-01-19 19:43:47 -08:00
|
|
|
|
|
|
|
// Next, Bob will process the closing signed message, and send back a
|
|
|
|
// new one that should match exactly the offer Alice sent.
|
2023-12-01 17:18:01 -08:00
|
|
|
oClosingSigned, err = bobCloser.ReceiveClosingSigned(aliceClosingSigned)
|
2023-01-19 19:43:47 -08:00
|
|
|
require.NoError(t, err)
|
2023-12-01 17:18:01 -08:00
|
|
|
tx, _ = bobCloser.ClosingTx()
|
|
|
|
require.NotNil(t, tx)
|
|
|
|
require.True(t, oClosingSigned.IsSome())
|
|
|
|
|
multi: upgrade new taproot TLVs to use tlv.OptionalRecordT
In this commit, we update new Taproot related TLVs (nonces, partial sig,
sig with nonce, etc). Along the way we were able to get rid of some
boiler plate, but most importantly, we're able to better protect against
API misuse (using a nonce that isn't initialized, etc) with the new
options API. In some areas this introduces a bit of extra boiler plate,
and where applicable I used some new helper functions to help cut down
on the noise.
Note to reviewers: this is done as a single commit, as changing the API
breaks all callers, so if we want things to compile it needs to be in a
wumbo commit.
2024-02-23 18:04:51 -08:00
|
|
|
bobClosingSigned := oClosingSigned.UnwrapOrFail(t)
|
2023-01-19 19:43:47 -08:00
|
|
|
|
|
|
|
// At this point, Bob has accepted the offer, so he can broadcast the
|
|
|
|
// closing transaction, and considers the channel closed.
|
|
|
|
_, err = lnutils.RecvOrTimeout(broadcastSignal, time.Second*1)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
// Bob's fee proposal should exactly match Alice's initial fee.
|
2023-12-01 17:18:01 -08:00
|
|
|
require.Equal(
|
|
|
|
t, aliceClosingSigned.FeeSatoshis, bobClosingSigned.FeeSatoshis,
|
|
|
|
)
|
2023-01-19 19:43:47 -08:00
|
|
|
|
|
|
|
// If we modify Bob's offer, and try to have Alice process it, then she
|
|
|
|
// should reject it.
|
2023-12-01 17:18:01 -08:00
|
|
|
ogOffer := bobClosingSigned.FeeSatoshis
|
|
|
|
bobClosingSigned.FeeSatoshis /= 2
|
2023-01-19 19:43:47 -08:00
|
|
|
|
2023-12-01 17:18:01 -08:00
|
|
|
_, err = aliceCloser.ReceiveClosingSigned(bobClosingSigned)
|
2023-01-19 19:43:47 -08:00
|
|
|
require.Error(t, err)
|
|
|
|
require.Contains(t, err.Error(), "was not accepted")
|
|
|
|
|
|
|
|
// We'll now restore the original offer before passing it on to Alice.
|
2023-12-01 17:18:01 -08:00
|
|
|
bobClosingSigned.FeeSatoshis = ogOffer
|
2023-01-19 19:43:47 -08:00
|
|
|
|
|
|
|
// If we use the original offer, then Alice should accept this message,
|
|
|
|
// and finalize the shutdown process. We expect a message here as Alice
|
|
|
|
// will echo back the final message.
|
2023-12-01 17:18:01 -08:00
|
|
|
oClosingSigned, err = aliceCloser.ReceiveClosingSigned(bobClosingSigned)
|
2023-01-19 19:43:47 -08:00
|
|
|
require.NoError(t, err)
|
2023-12-01 17:18:01 -08:00
|
|
|
tx, _ = aliceCloser.ClosingTx()
|
|
|
|
require.NotNil(t, tx)
|
|
|
|
require.True(t, oClosingSigned.IsSome())
|
|
|
|
|
multi: upgrade new taproot TLVs to use tlv.OptionalRecordT
In this commit, we update new Taproot related TLVs (nonces, partial sig,
sig with nonce, etc). Along the way we were able to get rid of some
boiler plate, but most importantly, we're able to better protect against
API misuse (using a nonce that isn't initialized, etc) with the new
options API. In some areas this introduces a bit of extra boiler plate,
and where applicable I used some new helper functions to help cut down
on the noise.
Note to reviewers: this is done as a single commit, as changing the API
breaks all callers, so if we want things to compile it needs to be in a
wumbo commit.
2024-02-23 18:04:51 -08:00
|
|
|
aliceClosingSigned = oClosingSigned.UnwrapOrFail(t)
|
2023-01-19 19:43:47 -08:00
|
|
|
|
|
|
|
// Alice should now also broadcast her closing transaction.
|
|
|
|
_, err = lnutils.RecvOrTimeout(broadcastSignal, time.Second*1)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
// Finally, Bob will process Alice's echo message, and conclude.
|
2023-12-01 17:18:01 -08:00
|
|
|
oClosingSigned, err = bobCloser.ReceiveClosingSigned(aliceClosingSigned)
|
2023-01-19 19:43:47 -08:00
|
|
|
require.NoError(t, err)
|
2023-12-01 17:18:01 -08:00
|
|
|
tx, _ = bobCloser.ClosingTx()
|
|
|
|
require.NotNil(t, tx)
|
|
|
|
require.True(t, oClosingSigned.IsNone())
|
2023-01-19 19:43:47 -08:00
|
|
|
}
|