mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-03-04 09:48:19 +01:00
We only want to allow p2wkh, p2tr, and p2wsh addresses, so we'll utilize the newly public wallet function to restrict this.
295 lines
7.5 KiB
Go
295 lines
7.5 KiB
Go
package chancloser
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"testing"
|
|
|
|
"github.com/btcsuite/btcd/btcutil"
|
|
"github.com/btcsuite/btcd/chaincfg"
|
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
|
"github.com/btcsuite/btcd/txscript"
|
|
"github.com/btcsuite/btcd/wire"
|
|
"github.com/lightningnetwork/lnd/input"
|
|
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
|
"github.com/lightningnetwork/lnd/lnwire"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// 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) {
|
|
t.Parallel()
|
|
|
|
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)
|
|
|
|
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 {
|
|
name string
|
|
shutdownScript lnwire.DeliveryAddress
|
|
upfrontScript lnwire.DeliveryAddress
|
|
expectedErr error
|
|
}
|
|
tests := []testCase{
|
|
{
|
|
name: "no upfront shutdown set, script ok",
|
|
shutdownScript: p2wkh,
|
|
upfrontScript: []byte{},
|
|
expectedErr: nil,
|
|
},
|
|
{
|
|
name: "upfront shutdown set, script ok",
|
|
shutdownScript: p2wkh,
|
|
upfrontScript: p2wkh,
|
|
expectedErr: nil,
|
|
},
|
|
{
|
|
name: "upfront shutdown set, script not ok",
|
|
shutdownScript: p2wkh,
|
|
upfrontScript: p2wsh,
|
|
expectedErr: ErrUpfrontShutdownScriptMismatch,
|
|
},
|
|
{
|
|
name: "nil shutdown and empty upfront",
|
|
shutdownScript: nil,
|
|
upfrontScript: []byte{},
|
|
expectedErr: nil,
|
|
},
|
|
{
|
|
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,
|
|
})
|
|
}
|
|
|
|
for _, test := range tests {
|
|
test := test
|
|
|
|
t.Run(test.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
err := validateShutdownScript(
|
|
func() error { return nil }, test.upfrontScript,
|
|
test.shutdownScript, &chaincfg.SimNetParams,
|
|
)
|
|
|
|
if err != test.expectedErr {
|
|
t.Fatalf("Error: %v, expected error: %v", err, test.expectedErr)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
type mockChannel struct {
|
|
absoluteFee btcutil.Amount
|
|
chanPoint wire.OutPoint
|
|
initiator bool
|
|
scid lnwire.ShortChannelID
|
|
}
|
|
|
|
func (m *mockChannel) CalcFee(chainfee.SatPerKWeight) btcutil.Amount {
|
|
return m.absoluteFee
|
|
}
|
|
|
|
func (m *mockChannel) ChannelPoint() *wire.OutPoint {
|
|
return &m.chanPoint
|
|
}
|
|
|
|
func (m *mockChannel) MarkCoopBroadcasted(*wire.MsgTx, bool) error {
|
|
return nil
|
|
}
|
|
|
|
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,
|
|
localScript, remoteScript []byte,
|
|
) (input.Signature, *chainhash.Hash, btcutil.Amount, error) {
|
|
|
|
return nil, nil, 0, nil
|
|
}
|
|
|
|
func (m *mockChannel) CompleteCooperativeClose(localSig,
|
|
remoteSig input.Signature, localScript, remoteScript []byte,
|
|
proposedFee btcutil.Amount) (*wire.MsgTx, btcutil.Amount, error) {
|
|
|
|
return nil, 0, nil
|
|
}
|
|
|
|
// 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()
|
|
|
|
const absoluteFee = btcutil.Amount(1000)
|
|
|
|
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),
|
|
maxFee: absoluteFee * defaultMaxFeeMultiplier,
|
|
}, {
|
|
// Max fee specified, this should be used in place.
|
|
name: "max fee clamp",
|
|
|
|
idealFee: chainfee.SatPerKWeight(253),
|
|
inputMaxFee: chainfee.SatPerKWeight(2530),
|
|
|
|
// Our mock just returns the canned absolute fee here.
|
|
maxFee: absoluteFee,
|
|
},
|
|
}
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
channel := mockChannel{
|
|
absoluteFee: absoluteFee,
|
|
}
|
|
|
|
chanCloser := NewChanCloser(
|
|
ChanCloseCfg{
|
|
Channel: &channel,
|
|
MaxFee: test.inputMaxFee,
|
|
}, nil, test.idealFee, 0, nil, false,
|
|
)
|
|
|
|
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} {
|
|
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{
|
|
absoluteFee: absoluteFee,
|
|
initiator: isInitiator,
|
|
},
|
|
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,
|
|
}
|
|
|
|
_, _, err := chanCloser.ProcessCloseMsg(closeMsg)
|
|
|
|
switch isInitiator {
|
|
// If we're the initiator, then we expect an error at
|
|
// this point.
|
|
case true:
|
|
require.ErrorIs(t, err, ErrProposalExeceedsMaxFee)
|
|
|
|
// Otherwise, we expect things to fail for some other
|
|
// reason (invalid sig, etc).
|
|
case false:
|
|
require.NotErrorIs(t, err, ErrProposalExeceedsMaxFee)
|
|
}
|
|
})
|
|
}
|
|
}
|