mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-01-18 21:35:24 +01:00
funding: add explicit commitment type negotiation support
This commit adds the ability for a channel initiator/responder to determine whether the channel to be opened can use a specific commitment type through explicit negotiation. It also includes the existing implicit negotiation logic to fall back on if explicit negotiation is not supported.
This commit is contained in:
parent
031d7b1d55
commit
e7885f2bde
120
funding/commitment_type_negotiation.go
Normal file
120
funding/commitment_type_negotiation.go
Normal file
@ -0,0 +1,120 @@
|
||||
package funding
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/lightningnetwork/lnd/lnwallet"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
)
|
||||
|
||||
var (
|
||||
// errUnsupportedExplicitNegotiation is an error returned when explicit
|
||||
// channel commitment negotiation is attempted but either peer of the
|
||||
// channel does not support it.
|
||||
errUnsupportedExplicitNegotiation = errors.New("explicit channel " +
|
||||
"type negotiation not supported")
|
||||
|
||||
// errUnsupportedCommitmentType is an error returned when a specific
|
||||
// channel commitment type is being explicitly negotiated but either
|
||||
// peer of the channel does not support it.
|
||||
errUnsupportedChannelType = errors.New("requested channel type " +
|
||||
"not supported")
|
||||
)
|
||||
|
||||
// negotiateCommitmentType negotiates the commitment type of a newly opened
|
||||
// channel. If a channelType is provided, explicit negotiation for said type
|
||||
// will be attempted if the set of both local and remote features support it.
|
||||
// Otherwise, implicit negotiation will be attempted.
|
||||
func negotiateCommitmentType(channelType *lnwire.ChannelType,
|
||||
local, remote *lnwire.FeatureVector) (lnwallet.CommitmentType, error) {
|
||||
|
||||
if channelType != nil {
|
||||
if !hasFeatures(local, remote, lnwire.ExplicitChannelTypeOptional) {
|
||||
return 0, errUnsupportedExplicitNegotiation
|
||||
}
|
||||
return explicitNegotiateCommitmentType(
|
||||
*channelType, local, remote,
|
||||
)
|
||||
}
|
||||
|
||||
return implicitNegotiateCommitmentType(local, remote), nil
|
||||
}
|
||||
|
||||
// explicitNegotiateCommitmentType attempts to explicitly negotiate for a
|
||||
// specific channel type. Since the channel type is comprised of a set of even
|
||||
// feature bits, we also make sure each feature is supported by both peers. An
|
||||
// error is returned if either peer does not support said channel type.
|
||||
func explicitNegotiateCommitmentType(channelType lnwire.ChannelType,
|
||||
local, remote *lnwire.FeatureVector) (lnwallet.CommitmentType, error) {
|
||||
|
||||
channelFeatures := lnwire.RawFeatureVector(channelType)
|
||||
|
||||
switch {
|
||||
// Anchors zero fee + static remote key features only.
|
||||
case channelFeatures.OnlyContains(
|
||||
lnwire.AnchorsZeroFeeHtlcTxRequired,
|
||||
lnwire.StaticRemoteKeyRequired,
|
||||
):
|
||||
if !hasFeatures(
|
||||
local, remote,
|
||||
lnwire.AnchorsZeroFeeHtlcTxOptional,
|
||||
lnwire.StaticRemoteKeyOptional,
|
||||
) {
|
||||
return 0, errUnsupportedChannelType
|
||||
}
|
||||
return lnwallet.CommitmentTypeAnchorsZeroFeeHtlcTx, nil
|
||||
|
||||
// Static remote key feature only.
|
||||
case channelFeatures.OnlyContains(lnwire.StaticRemoteKeyRequired):
|
||||
if !hasFeatures(local, remote, lnwire.StaticRemoteKeyOptional) {
|
||||
return 0, errUnsupportedChannelType
|
||||
}
|
||||
return lnwallet.CommitmentTypeTweakless, nil
|
||||
|
||||
// No features, use legacy commitment type.
|
||||
case channelFeatures.IsEmpty():
|
||||
return lnwallet.CommitmentTypeLegacy, nil
|
||||
|
||||
default:
|
||||
return 0, errUnsupportedChannelType
|
||||
}
|
||||
}
|
||||
|
||||
// implicitNegotiateCommitmentType negotiates the commitment type of a channel
|
||||
// implicitly by choosing the latest type supported by the local and remote
|
||||
// fetures.
|
||||
func implicitNegotiateCommitmentType(local,
|
||||
remote *lnwire.FeatureVector) lnwallet.CommitmentType {
|
||||
|
||||
// If both peers are signalling support for anchor commitments with
|
||||
// zero-fee HTLC transactions, we'll use this type.
|
||||
if hasFeatures(local, remote, lnwire.AnchorsZeroFeeHtlcTxOptional) {
|
||||
return lnwallet.CommitmentTypeAnchorsZeroFeeHtlcTx
|
||||
}
|
||||
|
||||
// Since we don't want to support the "legacy" anchor type, we will fall
|
||||
// back to static remote key if the nodes don't support the zero fee
|
||||
// HTLC tx anchor type.
|
||||
//
|
||||
// If both nodes are signaling the proper feature bit for tweakless
|
||||
// commitments, we'll use that.
|
||||
if hasFeatures(local, remote, lnwire.StaticRemoteKeyOptional) {
|
||||
return lnwallet.CommitmentTypeTweakless
|
||||
}
|
||||
|
||||
// Otherwise we'll fall back to the legacy type.
|
||||
return lnwallet.CommitmentTypeLegacy
|
||||
}
|
||||
|
||||
// hasFeatures determines whether a set of features is supported by both the set
|
||||
// of local and remote features.
|
||||
func hasFeatures(local, remote *lnwire.FeatureVector,
|
||||
features ...lnwire.FeatureBit) bool {
|
||||
|
||||
for _, feature := range features {
|
||||
if !local.HasFeature(feature) || !remote.HasFeature(feature) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
191
funding/commitment_type_negotiation_test.go
Normal file
191
funding/commitment_type_negotiation_test.go
Normal file
@ -0,0 +1,191 @@
|
||||
package funding
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/lightningnetwork/lnd/lnwallet"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// TestCommitmentTypeNegotiation tests all of the possible paths of a channel
|
||||
// commitment type negotiation.
|
||||
func TestCommitmentTypeNegotiation(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
channelFeatures *lnwire.RawFeatureVector
|
||||
localFeatures *lnwire.RawFeatureVector
|
||||
remoteFeatures *lnwire.RawFeatureVector
|
||||
expectsRes lnwallet.CommitmentType
|
||||
expectsErr error
|
||||
}{
|
||||
{
|
||||
name: "explicit missing remote negotiation feature",
|
||||
channelFeatures: lnwire.NewRawFeatureVector(
|
||||
lnwire.StaticRemoteKeyRequired,
|
||||
lnwire.AnchorsZeroFeeHtlcTxRequired,
|
||||
),
|
||||
localFeatures: lnwire.NewRawFeatureVector(
|
||||
lnwire.StaticRemoteKeyOptional,
|
||||
lnwire.AnchorsZeroFeeHtlcTxOptional,
|
||||
lnwire.ExplicitChannelTypeOptional,
|
||||
),
|
||||
remoteFeatures: lnwire.NewRawFeatureVector(
|
||||
lnwire.StaticRemoteKeyOptional,
|
||||
lnwire.AnchorsZeroFeeHtlcTxOptional,
|
||||
),
|
||||
expectsErr: errUnsupportedExplicitNegotiation,
|
||||
},
|
||||
{
|
||||
name: "explicit missing remote commitment feature",
|
||||
channelFeatures: lnwire.NewRawFeatureVector(
|
||||
lnwire.StaticRemoteKeyRequired,
|
||||
lnwire.AnchorsZeroFeeHtlcTxRequired,
|
||||
),
|
||||
localFeatures: lnwire.NewRawFeatureVector(
|
||||
lnwire.StaticRemoteKeyOptional,
|
||||
lnwire.AnchorsZeroFeeHtlcTxOptional,
|
||||
lnwire.ExplicitChannelTypeOptional,
|
||||
),
|
||||
remoteFeatures: lnwire.NewRawFeatureVector(
|
||||
lnwire.StaticRemoteKeyOptional,
|
||||
lnwire.ExplicitChannelTypeOptional,
|
||||
),
|
||||
expectsErr: errUnsupportedChannelType,
|
||||
},
|
||||
{
|
||||
name: "explicit anchors",
|
||||
channelFeatures: lnwire.NewRawFeatureVector(
|
||||
lnwire.StaticRemoteKeyRequired,
|
||||
lnwire.AnchorsZeroFeeHtlcTxRequired,
|
||||
),
|
||||
localFeatures: lnwire.NewRawFeatureVector(
|
||||
lnwire.StaticRemoteKeyOptional,
|
||||
lnwire.AnchorsZeroFeeHtlcTxOptional,
|
||||
lnwire.ExplicitChannelTypeOptional,
|
||||
),
|
||||
remoteFeatures: lnwire.NewRawFeatureVector(
|
||||
lnwire.StaticRemoteKeyOptional,
|
||||
lnwire.AnchorsZeroFeeHtlcTxOptional,
|
||||
lnwire.ExplicitChannelTypeOptional,
|
||||
),
|
||||
expectsRes: lnwallet.CommitmentTypeAnchorsZeroFeeHtlcTx,
|
||||
expectsErr: nil,
|
||||
},
|
||||
{
|
||||
name: "explicit tweakless",
|
||||
channelFeatures: lnwire.NewRawFeatureVector(
|
||||
lnwire.StaticRemoteKeyRequired,
|
||||
),
|
||||
localFeatures: lnwire.NewRawFeatureVector(
|
||||
lnwire.StaticRemoteKeyOptional,
|
||||
lnwire.AnchorsZeroFeeHtlcTxOptional,
|
||||
lnwire.ExplicitChannelTypeOptional,
|
||||
),
|
||||
remoteFeatures: lnwire.NewRawFeatureVector(
|
||||
lnwire.StaticRemoteKeyOptional,
|
||||
lnwire.AnchorsZeroFeeHtlcTxOptional,
|
||||
lnwire.ExplicitChannelTypeOptional,
|
||||
),
|
||||
expectsRes: lnwallet.CommitmentTypeTweakless,
|
||||
expectsErr: nil,
|
||||
},
|
||||
{
|
||||
name: "explicit legacy",
|
||||
channelFeatures: lnwire.NewRawFeatureVector(),
|
||||
localFeatures: lnwire.NewRawFeatureVector(
|
||||
lnwire.StaticRemoteKeyOptional,
|
||||
lnwire.AnchorsZeroFeeHtlcTxOptional,
|
||||
lnwire.ExplicitChannelTypeOptional,
|
||||
),
|
||||
remoteFeatures: lnwire.NewRawFeatureVector(
|
||||
lnwire.StaticRemoteKeyOptional,
|
||||
lnwire.AnchorsZeroFeeHtlcTxOptional,
|
||||
lnwire.ExplicitChannelTypeOptional,
|
||||
),
|
||||
expectsRes: lnwallet.CommitmentTypeLegacy,
|
||||
expectsErr: nil,
|
||||
},
|
||||
{
|
||||
name: "implicit anchors",
|
||||
channelFeatures: nil,
|
||||
localFeatures: lnwire.NewRawFeatureVector(
|
||||
lnwire.StaticRemoteKeyOptional,
|
||||
lnwire.AnchorsZeroFeeHtlcTxOptional,
|
||||
lnwire.ExplicitChannelTypeOptional,
|
||||
),
|
||||
remoteFeatures: lnwire.NewRawFeatureVector(
|
||||
lnwire.StaticRemoteKeyOptional,
|
||||
lnwire.AnchorsZeroFeeHtlcTxOptional,
|
||||
lnwire.ExplicitChannelTypeOptional,
|
||||
),
|
||||
expectsRes: lnwallet.CommitmentTypeAnchorsZeroFeeHtlcTx,
|
||||
expectsErr: nil,
|
||||
},
|
||||
{
|
||||
name: "implicit tweakless",
|
||||
channelFeatures: nil,
|
||||
localFeatures: lnwire.NewRawFeatureVector(
|
||||
lnwire.StaticRemoteKeyOptional,
|
||||
lnwire.AnchorsZeroFeeHtlcTxOptional,
|
||||
),
|
||||
remoteFeatures: lnwire.NewRawFeatureVector(
|
||||
lnwire.StaticRemoteKeyOptional,
|
||||
),
|
||||
expectsRes: lnwallet.CommitmentTypeTweakless,
|
||||
expectsErr: nil,
|
||||
},
|
||||
{
|
||||
name: "implicit legacy",
|
||||
channelFeatures: nil,
|
||||
localFeatures: lnwire.NewRawFeatureVector(),
|
||||
remoteFeatures: lnwire.NewRawFeatureVector(
|
||||
lnwire.StaticRemoteKeyOptional,
|
||||
lnwire.AnchorsZeroFeeHtlcTxOptional,
|
||||
),
|
||||
expectsRes: lnwallet.CommitmentTypeLegacy,
|
||||
expectsErr: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
testCase := testCase
|
||||
ok := t.Run(testCase.name, func(t *testing.T) {
|
||||
localFeatures := lnwire.NewFeatureVector(
|
||||
testCase.localFeatures, lnwire.Features,
|
||||
)
|
||||
remoteFeatures := lnwire.NewFeatureVector(
|
||||
testCase.remoteFeatures, lnwire.Features,
|
||||
)
|
||||
|
||||
var channelType *lnwire.ChannelType
|
||||
if testCase.channelFeatures != nil {
|
||||
channelType = new(lnwire.ChannelType)
|
||||
*channelType = lnwire.ChannelType(
|
||||
*testCase.channelFeatures,
|
||||
)
|
||||
}
|
||||
localType, err := negotiateCommitmentType(
|
||||
channelType, localFeatures, remoteFeatures,
|
||||
)
|
||||
require.Equal(t, testCase.expectsErr, err)
|
||||
|
||||
remoteType, err := negotiateCommitmentType(
|
||||
channelType, remoteFeatures, localFeatures,
|
||||
)
|
||||
require.Equal(t, testCase.expectsErr, err)
|
||||
|
||||
if testCase.expectsErr != nil {
|
||||
return
|
||||
}
|
||||
|
||||
require.Equal(t, testCase.expectsRes, localType)
|
||||
require.Equal(t, testCase.expectsRes, remoteType)
|
||||
})
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user