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:
Wilmer Paulino 2021-06-09 12:43:39 -07:00 committed by Olaoluwa Osuntokun
parent 031d7b1d55
commit e7885f2bde
No known key found for this signature in database
GPG Key ID: 3BBD59E99B280306
2 changed files with 311 additions and 0 deletions

View 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
}

View 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
}
}
}