Repository: lightningnetwork/lnd
Pull request #3685 (2019-11-07 02:12:16)

flat features
An implementation of https://github.com/lightningnetwork/lightning-rfc/pull/666


Patch:
diff --git a/autopilot/graph.go b/autopilot/graph.go
index 413aaf137..f02d0a8e6 100644
--- a/autopilot/graph.go
+++ b/autopilot/graph.go
@@ -157,8 +157,9 @@ func (d *databaseChannelGraph) addRandChannel(node1, node2 *btcec.PublicKey,
 							IP: bytes.Repeat([]byte("a"), 16),
 						},
 					},
-					Features: lnwire.NewFeatureVector(nil,
-						lnwire.GlobalFeatures),
+					Features: lnwire.NewFeatureVector(
+						nil, lnwire.Features,
+					),
 					AuthSigBytes: testSig.Serialize(),
 				}
 				graphNode.AddPubKey(pub)
@@ -183,7 +184,9 @@ func (d *databaseChannelGraph) addRandChannel(node1, node2 *btcec.PublicKey,
 					IP: bytes.Repeat([]byte("a"), 16),
 				},
 			},
-			Features:     lnwire.NewFeatureVector(nil, lnwire.GlobalFeatures),
+			Features: lnwire.NewFeatureVector(
+				nil, lnwire.Features,
+			),
 			AuthSigBytes: testSig.Serialize(),
 		}
 		dbNode.AddPubKey(nodeKey)
@@ -287,7 +290,9 @@ func (d *databaseChannelGraph) addRandNode() (*btcec.PublicKey, error) {
 				IP: bytes.Repeat([]byte("a"), 16),
 			},
 		},
-		Features:     lnwire.NewFeatureVector(nil, lnwire.GlobalFeatures),
+		Features: lnwire.NewFeatureVector(
+			nil, lnwire.Features,
+		),
 		AuthSigBytes: testSig.Serialize(),
 	}
 	dbNode.AddPubKey(nodeKey)
diff --git a/channeldb/graph.go b/channeldb/graph.go
index 46ba13111..275d6858b 100644
--- a/channeldb/graph.go
+++ b/channeldb/graph.go
@@ -3506,7 +3506,7 @@ func deserializeLightningNode(r io.Reader) (LightningNode, error) {
 		return LightningNode{}, err
 	}
 
-	fv := lnwire.NewFeatureVector(nil, lnwire.GlobalFeatures)
+	fv := lnwire.NewFeatureVector(nil, lnwire.Features)
 	err = fv.Decode(r)
 	if err != nil {
 		return LightningNode{}, err
diff --git a/channeldb/migration_01_to_11/graph.go b/channeldb/migration_01_to_11/graph.go
index 8e8f4a4a1..a782b9c45 100644
--- a/channeldb/migration_01_to_11/graph.go
+++ b/channeldb/migration_01_to_11/graph.go
@@ -744,7 +744,7 @@ func deserializeLightningNode(r io.Reader) (LightningNode, error) {
 		return LightningNode{}, err
 	}
 
-	fv := lnwire.NewFeatureVector(nil, lnwire.GlobalFeatures)
+	fv := lnwire.NewFeatureVector(nil, nil)
 	err = fv.Decode(r)
 	if err != nil {
 		return LightningNode{}, err
diff --git a/discovery/gossiper.go b/discovery/gossiper.go
index 5e8e02159..8c9ed6e59 100644
--- a/discovery/gossiper.go
+++ b/discovery/gossiper.go
@@ -1459,9 +1459,7 @@ func (d *AuthenticatedGossiper) addNode(msg *lnwire.NodeAnnouncement) error {
 	}
 
 	timestamp := time.Unix(int64(msg.Timestamp), 0)
-	features := lnwire.NewFeatureVector(
-		msg.Features, lnwire.GlobalFeatures,
-	)
+	features := lnwire.NewFeatureVector(msg.Features, lnwire.Features)
 	node := &channeldb.LightningNode{
 		HaveNodeAnnouncement: true,
 		LastUpdate:           timestamp,
diff --git a/feature/default_sets.go b/feature/default_sets.go
new file mode 100644
index 000000000..fe69226c3
--- /dev/null
+++ b/feature/default_sets.go
@@ -0,0 +1,32 @@
+package feature
+
+import "github.com/lightningnetwork/lnd/lnwire"
+
+// setDesc describes which feature bits should be advertised in which feature
+// sets.
+type setDesc map[lnwire.FeatureBit]map[Set]struct{}
+
+// defaultSetDesc are the default set descriptors for generating feature
+// vectors. Each set is annotated with the corresponding identifier from BOLT 9
+// indicating where it should be advertised.
+var defaultSetDesc = setDesc{
+	lnwire.DataLossProtectRequired: {
+		SetInit:    {}, // I
+		SetNodeAnn: {}, // N
+	},
+	lnwire.GossipQueriesOptional: {
+		SetInit:    {}, // I
+		SetNodeAnn: {}, // N
+	},
+	lnwire.TLVOnionPayloadOptional: {
+		SetInit:         {}, // I
+		SetNodeAnn:      {}, // N
+		SetInvoice:      {}, // 9
+		SetLegacyGlobal: {},
+	},
+	lnwire.StaticRemoteKeyOptional: {
+		SetInit:         {}, // I
+		SetNodeAnn:      {}, // N
+		SetLegacyGlobal: {},
+	},
+}
diff --git a/feature/manager.go b/feature/manager.go
new file mode 100644
index 000000000..f201cad1b
--- /dev/null
+++ b/feature/manager.go
@@ -0,0 +1,97 @@
+package feature
+
+import (
+	"fmt"
+
+	"github.com/lightningnetwork/lnd/lnwire"
+)
+
+// Config houses any runtime modifications to the default set descriptors. For
+// our purposes, this typically means disabling certain features to test legacy
+// protocol interoperability or functionality.
+type Config struct {
+	// NoTLVOnion unsets any optional or required TLVOnionPaylod bits from
+	// all feature sets.
+	NoTLVOnion bool
+
+	// NoStaticRemoteKey unsets any optional or required StaticRemoteKey
+	// bits from all feature sets.
+	NoStaticRemoteKey bool
+}
+
+// Manager is responsible for generating feature vectors for different requested
+// feature sets.
+type Manager struct {
+	// fsets is a static map of feature set to raw feature vectors. Requests
+	// are fulfilled by cloning these interal feature vectors.
+	fsets map[Set]*lnwire.RawFeatureVector
+}
+
+// NewManager creates a new feature Manager, applying any custom modifications
+// to its feature sets before returning.
+func NewManager(cfg Config) (*Manager, error) {
+	return newManager(cfg, defaultSetDesc)
+}
+
+// newManager creates a new feeature Manager, applying any custom modifications
+// to its feature sets before returning. This method accepts the setDesc as its
+// own parameter so that it can be unit tested.
+func newManager(cfg Config, desc setDesc) (*Manager, error) {
+	// First build the default feature vector for all known sets.
+	fsets := make(map[Set]*lnwire.RawFeatureVector)
+	for bit, sets := range desc {
+		for set := range sets {
+			// Fetch the feature vector for this set, allocating a
+			// new one if it doesn't exist.
+			fv, ok := fsets[set]
+			if !ok {
+				fv = lnwire.NewRawFeatureVector()
+			}
+
+			// Set the configured bit on the feature vector,
+			// ensuring that we don't set two feature bits for the
+			// same pair.
+			err := fv.SafeSet(bit)
+			if err != nil {
+				return nil, fmt.Errorf("unable to set "+
+					"%v in %v: %v", bit, set, err)
+			}
+
+			// Write the updated feature vector under its set.
+			fsets[set] = fv
+		}
+	}
+
+	// Now, remove any features as directed by the config.
+	for _, fv := range fsets {
+		if cfg.NoTLVOnion {
+			fv.Unset(lnwire.TLVOnionPayloadOptional)
+			fv.Unset(lnwire.TLVOnionPayloadRequired)
+		}
+		if cfg.NoStaticRemoteKey {
+			fv.Unset(lnwire.StaticRemoteKeyOptional)
+			fv.Unset(lnwire.StaticRemoteKeyRequired)
+		}
+	}
+
+	return &Manager{
+		fsets: fsets,
+	}, nil
+}
+
+// GetRaw returns a raw feature vector for the passed set. If no set is known,
+// an empty raw feature vector is returned.
+func (m *Manager) GetRaw(set Set) *lnwire.RawFeatureVector {
+	if fv, ok := m.fsets[set]; ok {
+		return fv.Clone()
+	}
+
+	return lnwire.NewRawFeatureVector()
+}
+
+// Get returns a feature vector for the passed set. If no set is known, an empty
+// feature vector is returned.
+func (m *Manager) Get(set Set) *lnwire.FeatureVector {
+	raw := m.GetRaw(set)
+	return lnwire.NewFeatureVector(raw, lnwire.Features)
+}
diff --git a/feature/set.go b/feature/set.go
new file mode 100644
index 000000000..2ac2ce52c
--- /dev/null
+++ b/feature/set.go
@@ -0,0 +1,41 @@
+package feature
+
+// Set is an enum identifying various feature sets, which separates the single
+// feature namespace into distinct categories depending what context a feature
+// vector is being used.
+type Set uint8
+
+const (
+	// SetInit identifies features that should be sent in an Init message to
+	// a remote peer.
+	SetInit Set = iota
+
+	// SetLegacyGlobal identifies features that should be set in the legacy
+	// GlobalFeatures field of an Init message, which maintains backwards
+	// compatibility with nodes that haven't implemented flat features.
+	SetLegacyGlobal
+
+	// SetNodeAnn identifies features that should be advertised on node
+	// announcements.
+	SetNodeAnn
+
+	// SetInvoice identifies features that should be advertised on invoices
+	// generated by the daemon.
+	SetInvoice
+)
+
+// String returns a human-readable description of a Set.
+func (s Set) String() string {
+	switch s {
+	case SetInit:
+		return "SetInit"
+	case SetLegacyGlobal:
+		return "SetLegacyGlobal"
+	case SetNodeAnn:
+		return "SetNodeAnn"
+	case SetInvoice:
+		return "SetInvoice"
+	default:
+		return "SetUnknown"
+	}
+}
diff --git a/fundingmanager.go b/fundingmanager.go
index f33ce498f..ca7959d0c 100644
--- a/fundingmanager.go
+++ b/fundingmanager.go
@@ -1205,10 +1205,10 @@ func (f *fundingManager) handleFundingOpen(fmsg *fundingOpenMsg) {
 	// negotiated the new tweakless commitment format. This is only the
 	// case if *both* us and the remote peer are signaling the proper
 	// feature bit.
-	localTweakless := fmsg.peer.LocalGlobalFeatures().HasFeature(
+	localTweakless := fmsg.peer.LocalFeatures().HasFeature(
 		lnwire.StaticRemoteKeyOptional,
 	)
-	remoteTweakless := fmsg.peer.RemoteGlobalFeatures().HasFeature(
+	remoteTweakless := fmsg.peer.RemoteFeatures().HasFeature(
 		lnwire.StaticRemoteKeyOptional,
 	)
 	tweaklessCommitment := localTweakless && remoteTweakless
@@ -2780,10 +2780,10 @@ func (f *fundingManager) handleInitFundingMsg(msg *initFundingMsg) {
 	// negotiated the new tweakless commitment format. This is only the
 	// case if *both* us and the remote peer are signaling the proper
 	// feature bit.
-	localTweakless := msg.peer.LocalGlobalFeatures().HasFeature(
+	localTweakless := msg.peer.LocalFeatures().HasFeature(
 		lnwire.StaticRemoteKeyOptional,
 	)
-	remoteTweakless := msg.peer.RemoteGlobalFeatures().HasFeature(
+	remoteTweakless := msg.peer.RemoteFeatures().HasFeature(
 		lnwire.StaticRemoteKeyOptional,
 	)
 	tweaklessCommitment := localTweakless && remoteTweakless
diff --git a/htlcswitch/mock.go b/htlcswitch/mock.go
index 3dc1160df..c63aa1713 100644
--- a/htlcswitch/mock.go
+++ b/htlcswitch/mock.go
@@ -607,11 +607,11 @@ func (s *mockServer) WipeChannel(*wire.OutPoint) error {
 	return nil
 }
 
-func (s *mockServer) LocalGlobalFeatures() *lnwire.FeatureVector {
+func (s *mockServer) LocalFeatures() *lnwire.FeatureVector {
 	return nil
 }
 
-func (s *mockServer) RemoteGlobalFeatures() *lnwire.FeatureVector {
+func (s *mockServer) RemoteFeatures() *lnwire.FeatureVector {
 	return nil
 }
 
diff --git a/lnpeer/peer.go b/lnpeer/peer.go
index e21e27968..53f571314 100644
--- a/lnpeer/peer.go
+++ b/lnpeer/peer.go
@@ -47,15 +47,14 @@ type Peer interface {
 	// implementation exits.
 	QuitSignal() <-chan struct{}
 
-	// LocalGlobalFeatures returns the set of global features that has been
-	// advertised by the local peer. This allows sub-systems that use this
+	// LocalFeatures returns the set of features that has been advertised by
+	// the us to the remote peer. This allows sub-systems that use this
 	// interface to gate their behavior off the set of negotiated feature
 	// bits.
-	LocalGlobalFeatures() *lnwire.FeatureVector
+	LocalFeatures() *lnwire.FeatureVector
 
-	// RemoteGlobalFeatures returns the set of global features that has
-	// been advertised by the remote peer. This allows sub-systems that use
-	// this interface to gate their behavior off the set of negotiated
-	// feature bits.
-	RemoteGlobalFeatures() *lnwire.FeatureVector
+	// RemoteFeatures returns the set of features that has been advertised
+	// by the remote peer. This allows sub-systems that use this interface
+	// to gate their behavior off the set of negotiated feature bits.
+	RemoteFeatures() *lnwire.FeatureVector
 }
diff --git a/lnwire/features.go b/lnwire/features.go
index 9a34a6373..7700e2d5d 100644
--- a/lnwire/features.go
+++ b/lnwire/features.go
@@ -2,10 +2,17 @@ package lnwire
 
 import (
 	"encoding/binary"
+	"errors"
 	"fmt"
 	"io"
 )
 
+var (
+	// ErrFeaturePairExists signals an error in feature vector construction
+	// where the opposing bit in a feature pair has already been set.
+	ErrFeaturePairExists = errors.New("feature pair exists")
+)
+
 // FeatureBit represents a feature that can be enabled in either a local or
 // global feature vector at a specific bit position. Feature bits follow the
 // "it's OK to be odd" rule, where features at even bit positions must be known
@@ -78,24 +85,15 @@ const (
 	maxAllowedSize = 32764
 )
 
-// LocalFeatures is a mapping of known connection-local feature bits to a
-// descriptive name. All known local feature bits must be assigned a name in
-// this mapping. Local features are those which are only sent to the peer and
-// not advertised to the entire network. A full description of these feature
-// bits is provided in the BOLT-09 specification.
-var LocalFeatures = map[FeatureBit]string{
+// Features is a mapping of known feature bits to a descriptive name. All known
+// feature bits must be assigned a name in this mapping, and feature bit pairs
+// must be assigned together for correct behavior.
+var Features = map[FeatureBit]string{
 	DataLossProtectRequired: "data-loss-protect",
 	DataLossProtectOptional: "data-loss-protect",
 	InitialRoutingSync:      "initial-routing-sync",
 	GossipQueriesRequired:   "gossip-queries",
 	GossipQueriesOptional:   "gossip-queries",
-}
-
-// GlobalFeatures is a mapping of known global feature bits to a descriptive
-// name. All known global feature bits must be assigned a name in this mapping.
-// Global features are those which are advertised to the entire network. A full
-// description of these feature bits is provided in the BOLT-09 specification.
-var GlobalFeatures = map[FeatureBit]string{
 	TLVOnionPayloadRequired: "tlv-onion",
 	TLVOnionPayloadOptional: "tlv-onion",
 	StaticRemoteKeyOptional: "static-remote-key",
@@ -121,6 +119,26 @@ func NewRawFeatureVector(bits ...FeatureBit) *RawFeatureVector {
 	return fv
 }
 
+// Merges sets all feature bits in other on the receiver's feature vector.
+func (fv *RawFeatureVector) Merge(other *RawFeatureVector) error {
+	for bit := range other.features {
+		err := fv.SafeSet(bit)
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+// Clone makes a copy of a feature vector.
+func (fv *RawFeatureVector) Clone() *RawFeatureVector {
+	newFeatures := NewRawFeatureVector()
+	for bit := range fv.features {
+		newFeatures.Set(bit)
+	}
+	return newFeatures
+}
+
 // IsSet returns whether a particular feature bit is enabled in the vector.
 func (fv *RawFeatureVector) IsSet(feature FeatureBit) bool {
 	return fv.features[feature]
@@ -131,6 +149,20 @@ func (fv *RawFeatureVector) Set(feature FeatureBit) {
 	fv.features[feature] = true
 }
 
+// SafeSet sets the chosen feature bit in the feature vector, but returns an
+// error if the opposing feature bit is already set. This ensures both that we
+// are creating properly structured feature vectors, and in some cases, that
+// peers are sending properly encoded ones, i.e. it can't be both optional and
+// required.
+func (fv *RawFeatureVector) SafeSet(feature FeatureBit) error {
+	if _, ok := fv.features[feature^1]; ok {
+		return ErrFeaturePairExists
+	}
+
+	fv.Set(feature)
+	return nil
+}
+
 // Unset marks a feature as disabled in the vector.
 func (fv *RawFeatureVector) Unset(feature FeatureBit) {
 	delete(fv.features, feature)
diff --git a/lnwire/init_message.go b/lnwire/init_message.go
index f64f9f8ca..0236a71f8 100644
--- a/lnwire/init_message.go
+++ b/lnwire/init_message.go
@@ -7,20 +7,26 @@ import "io"
 // diagnosis where features are incompatible. Each node MUST wait to receive
 // init before sending any other messages.
 type Init struct {
-	// GlobalFeatures is feature vector which affects HTLCs and thus are
-	// also advertised to other nodes.
+	// GlobalFeatures is a legacy feature vector used for backwards
+	// compatibility with older nodes. Any features defined here should be
+	// merged with those presented in Features.
 	GlobalFeatures *RawFeatureVector
 
-	// LocalFeatures is feature vector which only affect the protocol
-	// between two nodes.
-	LocalFeatures *RawFeatureVector
+	// Features is a feature vector containing a the features supported by
+	// the remote node.
+	//
+	// NOTE: Older nodes may place some features in GlobalFeatures, but all
+	// new features are to be added in Features. When handling an Init
+	// message, any GlobalFeatures should be merged into the unified
+	// Features field.
+	Features *RawFeatureVector
 }
 
 // NewInitMessage creates new instance of init message object.
-func NewInitMessage(gf *RawFeatureVector, lf *RawFeatureVector) *Init {
+func NewInitMessage(gf *RawFeatureVector, f *RawFeatureVector) *Init {
 	return &Init{
 		GlobalFeatures: gf,
-		LocalFeatures:  lf,
+		Features:       f,
 	}
 }
 
@@ -35,7 +41,7 @@ var _ Message = (*Init)(nil)
 func (msg *Init) Decode(r io.Reader, pver uint32) error {
 	return ReadElements(r,
 		&msg.GlobalFeatures,
-		&msg.LocalFeatures,
+		&msg.Features,
 	)
 }
 
@@ -46,7 +52,7 @@ func (msg *Init) Decode(r io.Reader, pver uint32) error {
 func (msg *Init) Encode(w io.Writer, pver uint32) error {
 	return WriteElements(w,
 		msg.GlobalFeatures,
-		msg.LocalFeatures,
+		msg.Features,
 	)
 }
 
diff --git a/peer.go b/peer.go
index ade90b41b..a007701b7 100644
--- a/peer.go
+++ b/peer.go
@@ -190,21 +190,24 @@ type peer struct {
 
 	server *server
 
-	// localFeatures is the set of local features that we advertised to the
-	// remote node.
-	localFeatures *lnwire.RawFeatureVector
+	// features is the set of features that we advertised to the remote
+	// node.
+	features *lnwire.FeatureVector
+
+	// legacyFeatures is the set of features that we advertised to the remote
+	// node for backwards compatibility. Nodes that have not implemented
+	// flat featurs will still be able to read our feature bits from the
+	// legacy global field, but we will also advertise everything in the
+	// default features field.
+	legacyFeatures *lnwire.FeatureVector
 
 	// outgoingCltvRejectDelta defines the number of blocks before expiry of
 	// an htlc where we don't offer an htlc anymore.
 	outgoingCltvRejectDelta uint32
 
-	// remoteLocalFeatures is the local feature vector received from the
-	// peer during the connection handshake.
-	remoteLocalFeatures *lnwire.FeatureVector
-
-	// remoteGlobalFeatures is the global feature vector received from the
-	// peer during the connection handshake.
-	remoteGlobalFeatures *lnwire.FeatureVector
+	// remoteFeatures is the feature vector received from the peer during
+	// the connection handshake.
+	remoteFeatures *lnwire.FeatureVector
 
 	// failedChannels is a set that tracks channels we consider `failed`.
 	// This is a temporary measure until we have implemented real failure
@@ -234,7 +237,7 @@ var _ lnpeer.Peer = (*peer)(nil)
 // pointer to the main server.
 func newPeer(conn net.Conn, connReq *connmgr.ConnReq, server *server,
 	addr *lnwire.NetAddress, inbound bool,
-	localFeatures *lnwire.RawFeatureVector,
+	features, legacyFeatures *lnwire.FeatureVector,
 	chanActiveTimeout time.Duration,
 	outgoingCltvRejectDelta uint32) (
 	*peer, error) {
@@ -252,7 +255,8 @@ func newPeer(conn net.Conn, connReq *connmgr.ConnReq, server *server,
 
 		server: server,
 
-		localFeatures: localFeatures,
+		features:       features,
+		legacyFeatures: legacyFeatures,
 
 		outgoingCltvRejectDelta: outgoingCltvRejectDelta,
 
@@ -399,7 +403,7 @@ func (p *peer) initGossipSync() {
 
 	// If the remote peer knows of the new gossip queries feature, then
 	// we'll create a new gossipSyncer in the AuthenticatedGossiper for it.
-	case p.remoteLocalFeatures.HasFeature(lnwire.GossipQueriesOptional):
+	case p.remoteFeatures.HasFeature(lnwire.GossipQueriesOptional):
 		srvrLog.Infof("Negotiated chan series queries with %x",
 			p.pubKeyBytes[:])
 
@@ -2387,62 +2391,63 @@ func (p *peer) WipeChannel(chanPoint *wire.OutPoint) error {
 // handleInitMsg handles the incoming init message which contains global and
 // local features vectors. If feature vectors are incompatible then disconnect.
 func (p *peer) handleInitMsg(msg *lnwire.Init) error {
-	p.remoteLocalFeatures = lnwire.NewFeatureVector(
-		msg.LocalFeatures, lnwire.LocalFeatures,
-	)
-	p.remoteGlobalFeatures = lnwire.NewFeatureVector(
-		msg.GlobalFeatures, lnwire.GlobalFeatures,
+	// First, merge any features from the legacy global features field into
+	// those presented in the local features fields.
+	err := msg.Features.Merge(msg.GlobalFeatures)
+	if err != nil {
+		return fmt.Errorf("unable to merge legacy global featues: %v",
+			err)
+	}
+
+	// Then, finalize the remote feature vector providing the flatteneed
+	// feature bit namespace.
+	p.remoteFeatures = lnwire.NewFeatureVector(
+		msg.Features, lnwire.Features,
 	)
 
 	// Now that we have their features loaded, we'll ensure that they
 	// didn't set any required bits that we don't know of.
-	unknownLocalFeatures := p.remoteLocalFeatures.UnknownRequiredFeatures()
-	if len(unknownLocalFeatures) > 0 {
-		err := fmt.Errorf("Peer set unknown local feature bits: %v",
-			unknownLocalFeatures)
-		return err
-	}
-	unknownGlobalFeatures := p.remoteGlobalFeatures.UnknownRequiredFeatures()
-	if len(unknownGlobalFeatures) > 0 {
-		err := fmt.Errorf("Peer set unknown global feature bits: %v",
-			unknownGlobalFeatures)
+	unknownFeatures := p.remoteFeatures.UnknownRequiredFeatures()
+	if len(unknownFeatures) > 0 {
+		err := fmt.Errorf("peer set unknown feature bits: %v",
+			unknownFeatures)
 		return err
 	}
 
 	// Now that we know we understand their requirements, we'll check to
 	// see if they don't support anything that we deem to be mandatory.
 	switch {
-	case !p.remoteLocalFeatures.HasFeature(lnwire.DataLossProtectRequired):
+	case !p.remoteFeatures.HasFeature(lnwire.DataLossProtectRequired):
 		return fmt.Errorf("data loss protection required")
 	}
 
 	return nil
 }
 
-// LocalGlobalFeatures returns the set of global features that has been
-// advertised by the local node. This allows sub-systems that use this
-// interface to gate their behavior off the set of negotiated feature bits.
+// LocalFeatures returns the set of global features that has been advertised by
+// the local node. This allows sub-systems that use this interface to gate their
+// behavior off the set of negotiated feature bits.
 //
 // NOTE: Part of the lnpeer.Peer interface.
-func (p *peer) LocalGlobalFeatures() *lnwire.FeatureVector {
-	return p.server.globalFeatures
+func (p *peer) LocalFeatures() *lnwire.FeatureVector {
+	return p.features
 }
 
-// RemoteGlobalFeatures returns the set of global features that has been
-// advertised by the remote node. This allows sub-systems that use this
-// interface to gate their behavior off the set of negotiated feature bits.
+// RemoteFeatures returns the set of global features that has been advertised by
+// the remote node. This allows sub-systems that use this interface to gate
+// their behavior off the set of negotiated feature bits.
 //
 // NOTE: Part of the lnpeer.Peer interface.
-func (p *peer) RemoteGlobalFeatures() *lnwire.FeatureVector {
-	return p.remoteGlobalFeatures
+func (p *peer) RemoteFeatures() *lnwire.FeatureVector {
+	return p.remoteFeatures
 }
 
 // sendInitMsg sends init message to remote peer which contains our currently
 // supported local and global features.
 func (p *peer) sendInitMsg() error {
 	msg := lnwire.NewInitMessage(
-		p.server.globalFeatures.RawFeatureVector,
-		p.localFeatures,
+		p.legacyFeatures.RawFeatureVector,
+		p.features.RawFeatureVector,
 	)
 
 	return p.writeMessage(msg)
diff --git a/routing/missioncontrol.go b/routing/missioncontrol.go
index 2d5deb508..cebac51c4 100644
--- a/routing/missioncontrol.go
+++ b/routing/missioncontrol.go
@@ -118,9 +118,6 @@ type MissionControlConfig struct {
 	// probability completely and only base the probability on historical
 	// results, unless there are none available.
 	AprioriWeight float64
-
-	// SelfNode is our own pubkey.
-	SelfNode route.Vertex
 }
 
 // TimedPairResult describes a timestamped pair result.
@@ -264,11 +261,6 @@ func (m *MissionControl) GetProbability(fromNode, toNode route.Vertex,
 	now := m.now()
 	results := m.lastPairResult[fromNode]
 
-	// Use a distinct probability estimation function for local channels.
-	if fromNode == m.cfg.SelfNode {
-		return m.estimator.getLocalPairProbability(now, results, toNode)
-	}
-
 	return m.estimator.getPairProbability(now, results, toNode, amt)
 }
 
diff --git a/routing/probability_estimator.go b/routing/probability_estimator.go
index 1e60608cb..991a77562 100644
--- a/routing/probability_estimator.go
+++ b/routing/probability_estimator.go
@@ -116,43 +116,13 @@ func (p *probabilityEstimator) getPairProbability(
 	now time.Time, results NodeResults,
 	toNode route.Vertex, amt lnwire.MilliSatoshi) float64 {
 
-	nodeProbability := p.getNodeProbability(now, results, amt)
-
-	return p.calculateProbability(
-		now, results, nodeProbability, toNode, amt,
-	)
-}
-
-// getLocalPairProbability estimates the probability of successfully traversing
-// our own local channels to toNode.
-func (p *probabilityEstimator) getLocalPairProbability(
-	now time.Time, results NodeResults, toNode route.Vertex) float64 {
-
-	// For local channels that have never been tried before, we assume them
-	// to be successful. We have accurate balance and online status
-	// information on our own channels, so when we select them in a route it
-	// is close to certain that those channels will work.
-	nodeProbability := p.prevSuccessProbability
-
-	return p.calculateProbability(
-		now, results, nodeProbability, toNode, lnwire.MaxMilliSatoshi,
-	)
-}
-
-// calculateProbability estimates the probability of successfully traversing to
-// toNode based on historical payment outcomes and a fall-back node probability.
-func (p *probabilityEstimator) calculateProbability(
-	now time.Time, results NodeResults,
-	nodeProbability float64, toNode route.Vertex,
-	amt lnwire.MilliSatoshi) float64 {
-
 	// Retrieve the last pair outcome.
 	lastPairResult, ok := results[toNode]
 
 	// If there is no history for this pair, return the node probability
 	// that is a probability estimate for untried channel.
 	if !ok {
-		return nodeProbability
+		return p.getNodeProbability(now, results, amt)
 	}
 
 	// For successes, we have a fixed (high) probability. Those pairs
@@ -161,6 +131,8 @@ func (p *probabilityEstimator) calculateProbability(
 		return p.prevSuccessProbability
 	}
 
+	nodeProbability := p.getNodeProbability(now, results, amt)
+
 	// Take into account a minimum penalize amount. For balance errors, a
 	// failure may be reported with such a minimum to prevent too aggressive
 	// penalization. If the current amount is smaller than the amount that
diff --git a/server.go b/server.go
index 7baf14468..87fa17dc1 100644
--- a/server.go
+++ b/server.go
@@ -35,6 +35,7 @@ import (
 	"github.com/lightningnetwork/lnd/channelnotifier"
 	"github.com/lightningnetwork/lnd/contractcourt"
 	"github.com/lightningnetwork/lnd/discovery"
+	"github.com/lightningnetwork/lnd/feature"
 	"github.com/lightningnetwork/lnd/htlcswitch"
 	"github.com/lightningnetwork/lnd/htlcswitch/hop"
 	"github.com/lightningnetwork/lnd/input"
@@ -232,9 +233,9 @@ type server struct {
 
 	readPool *pool.Read
 
-	// globalFeatures feature vector which affects HTLCs and thus are also
-	// advertised to other nodes.
-	globalFeatures *lnwire.FeatureVector
+	// featureMgr dispatches feature vectors for various contexts within the
+	// daemon.
+	featureMgr *feature.Manager
 
 	// currentNodeAnn is the node announcement that has been broadcast to
 	// the network upon startup, if the attributes of the node (us) has
@@ -369,6 +370,14 @@ func newServer(listenAddrs []net.Addr, chanDB *channeldb.DB,
 		readBufferPool, cfg.Workers.Read, pool.DefaultWorkerTimeout,
 	)
 
+	featureMgr, err := feature.NewManager(feature.Config{
+		NoTLVOnion:        cfg.LegacyProtocol.LegacyOnion(),
+		NoStaticRemoteKey: cfg.LegacyProtocol.LegacyCommitment(),
+	})
+	if err != nil {
+		return nil, err
+	}
+
 	s := &server{
 		chanDB:         chanDB,
 		cc:             cc,
@@ -405,10 +414,8 @@ func newServer(listenAddrs []net.Addr, chanDB *channeldb.DB,
 		peerConnectedListeners:    make(map[string][]chan<- lnpeer.Peer),
 		peerDisconnectedListeners: make(map[string][]chan<- struct{}),
 
-		globalFeatures: lnwire.NewFeatureVector(
-			globalFeatures, lnwire.GlobalFeatures,
-		),
-		quit: make(chan struct{}),
+		featureMgr: featureMgr,
+		quit:       make(chan struct{}),
 	}
 
 	s.witnessBeacon = &preimageBeacon{
@@ -594,7 +601,7 @@ func newServer(listenAddrs []net.Addr, chanDB *channeldb.DB,
 		LastUpdate:           time.Now(),
 		Addresses:            selfAddrs,
 		Alias:                nodeAlias.String(),
-		Features:             s.globalFeatures,
+		Features:             s.featureMgr.Get(feature.SetNodeAnn),
 		Color:                color,
 	}
 	copy(selfNode.PubKeyBytes[:], privKey.PubKey().SerializeCompressed())
@@ -672,7 +679,6 @@ func newServer(listenAddrs []net.Addr, chanDB *channeldb.DB,
 			PenaltyHalfLife:       routingConfig.PenaltyHalfLife,
 			MaxMcHistory:          routingConfig.MaxMcHistory,
 			AprioriWeight:         routingConfig.AprioriWeight,
-			SelfNode:              selfNode.PubKeyBytes,
 		},
 	)
 	if err != nil {
@@ -1965,7 +1971,7 @@ func (s *server) initTorController() error {
 		Addresses:            newNodeAnn.Addresses,
 		Alias:                newNodeAnn.Alias.String(),
 		Features: lnwire.NewFeatureVector(
-			newNodeAnn.Features, lnwire.GlobalFeatures,
+			newNodeAnn.Features, lnwire.Features,
 		),
 		Color:        newNodeAnn.RGBColor,
 		AuthSigBytes: newNodeAnn.Signature.ToSignatureBytes(),
@@ -2728,14 +2734,10 @@ func (s *server) peerConnected(conn net.Conn, connReq *connmgr.ConnReq,
 		ChainNet:    activeNetParams.Net,
 	}
 
-	// With the brontide connection established, we'll now craft the local
-	// feature vector to advertise to the remote node.
-	localFeatures := lnwire.NewRawFeatureVector()
-
-	// We'll signal that we understand the data loss protection feature,
-	// and also that we support the new gossip query features.
-	localFeatures.Set(lnwire.DataLossProtectRequired)
-	localFeatures.Set(lnwire.GossipQueriesOptional)
+	// With the brontide connection established, we'll now craft the feature
+	// vectors to advertise to the remote node.
+	initFeatures := s.featureMgr.Get(feature.SetInit)
+	legacyFeatures := s.featureMgr.Get(feature.SetLegacyGlobal)
 
 	// Now that we've established a connection, create a peer, and it to the
 	// set of currently active peers. Configure the peer with the incoming
@@ -2744,8 +2746,8 @@ func (s *server) peerConnected(conn net.Conn, connReq *connmgr.ConnReq,
 	// htlcs, an extra block is added to prevent the channel from being
 	// closed when the htlc is outstanding and a new block comes in.
 	p, err := newPeer(
-		conn, connReq, s, peerAddr, inbound, localFeatures,
-		cfg.ChanEnableTimeout,
+		conn, connReq, s, peerAddr, inbound, initFeatures,
+		legacyFeatures, cfg.ChanEnableTimeout,
 		defaultOutgoingCltvRejectDelta,
 	)
 	if err != nil {


Test patch:
diff --git a/channeldb/graph_test.go b/channeldb/graph_test.go
index de8774a98..0768e8293 100644
--- a/channeldb/graph_test.go
+++ b/channeldb/graph_test.go
@@ -36,7 +36,7 @@ var (
 	_, _ = testSig.R.SetString("63724406601629180062774974542967536251589935445068131219452686511677818569431", 10)
 	_, _ = testSig.S.SetString("18801056069249825825291287104931333862866033135609736119018462340006816851118", 10)
 
-	testFeatures = lnwire.NewFeatureVector(nil, lnwire.GlobalFeatures)
+	testFeatures = lnwire.NewFeatureVector(nil, lnwire.Features)
 )
 
 func createLightningNode(db *DB, priv *btcec.PrivateKey) (*LightningNode, error) {
diff --git a/channeldb/migration_01_to_11/graph_test.go b/channeldb/migration_01_to_11/graph_test.go
index a65f00463..dc21fccfd 100644
--- a/channeldb/migration_01_to_11/graph_test.go
+++ b/channeldb/migration_01_to_11/graph_test.go
@@ -25,7 +25,7 @@ var (
 	_, _ = testSig.R.SetString("63724406601629180062774974542967536251589935445068131219452686511677818569431", 10)
 	_, _ = testSig.S.SetString("18801056069249825825291287104931333862866033135609736119018462340006816851118", 10)
 
-	testFeatures = lnwire.NewFeatureVector(nil, lnwire.GlobalFeatures)
+	testFeatures = lnwire.NewFeatureVector(nil, nil)
 )
 
 func createLightningNode(db *DB, priv *btcec.PrivateKey) (*LightningNode, error) {
diff --git a/discovery/mock_test.go b/discovery/mock_test.go
index 57fc319a5..f3f707d50 100644
--- a/discovery/mock_test.go
+++ b/discovery/mock_test.go
@@ -56,10 +56,10 @@ func (p *mockPeer) Address() net.Addr { return nil }
 func (p *mockPeer) QuitSignal() <-chan struct{} {
 	return p.quit
 }
-func (p *mockPeer) LocalGlobalFeatures() *lnwire.FeatureVector {
+func (p *mockPeer) LocalFeatures() *lnwire.FeatureVector {
 	return nil
 }
-func (p *mockPeer) RemoteGlobalFeatures() *lnwire.FeatureVector {
+func (p *mockPeer) RemoteFeatures() *lnwire.FeatureVector {
 	return nil
 }
 
diff --git a/feature/manager_internal_test.go b/feature/manager_internal_test.go
new file mode 100644
index 000000000..9c97bd052
--- /dev/null
+++ b/feature/manager_internal_test.go
@@ -0,0 +1,128 @@
+package feature
+
+import (
+	"reflect"
+	"testing"
+
+	"github.com/lightningnetwork/lnd/lnwire"
+)
+
+type managerTest struct {
+	name string
+	cfg  Config
+}
+
+const unknownFeature lnwire.FeatureBit = 30
+
+var testSetDesc = setDesc{
+	lnwire.DataLossProtectRequired: {
+		SetNodeAnn: {}, // I
+	},
+	lnwire.TLVOnionPayloadOptional: {
+		SetInit:    {}, // I
+		SetNodeAnn: {}, // N
+	},
+	lnwire.StaticRemoteKeyOptional: {
+		SetInit:    {}, // I
+		SetNodeAnn: {}, // N
+	},
+}
+
+var managerTests = []managerTest{
+	{
+		name: "default",
+		cfg:  Config{},
+	},
+	{
+		name: "no tlv",
+		cfg: Config{
+			NoTLVOnion: true,
+		},
+	},
+	{
+		name: "no static remote key",
+		cfg: Config{
+			NoStaticRemoteKey: true,
+		},
+	},
+	{
+		name: "no tlv or static remote key",
+		cfg: Config{
+			NoTLVOnion:        true,
+			NoStaticRemoteKey: true,
+		},
+	},
+}
+
+// TestManager asserts basic initialazation and operation of a feature manager,
+// including that the proper features are removed in response to config changes.
+func TestManager(t *testing.T) {
+	for _, test := range managerTests {
+		test := test
+		t.Run(test.name, func(t *testing.T) {
+			testManager(t, test)
+		})
+	}
+}
+
+func testManager(t *testing.T, test managerTest) {
+	m, err := newManager(test.cfg, testSetDesc)
+	if err != nil {
+		t.Fatalf("unable to create feature manager: %v", err)
+	}
+
+	sets := []Set{
+		SetInit,
+		SetLegacyGlobal,
+		SetNodeAnn,
+		SetInvoice,
+	}
+
+	for _, set := range sets {
+		raw := m.GetRaw(set)
+		fv := m.Get(set)
+
+		fv2 := lnwire.NewFeatureVector(raw, lnwire.Features)
+
+		if !reflect.DeepEqual(fv, fv2) {
+			t.Fatalf("mismatch Get vs GetRaw, raw: %v vs fv: %v",
+				fv2, fv)
+		}
+
+		assertUnset := func(bit lnwire.FeatureBit) {
+			hasBit := fv.HasFeature(bit) || fv.HasFeature(bit^1)
+			if hasBit {
+				t.Fatalf("bit %v or %v is set", bit, bit^1)
+			}
+		}
+
+		// Assert that the manager properly unset the configured feature
+		// bits from all sets.
+		if test.cfg.NoTLVOnion {
+			assertUnset(lnwire.TLVOnionPayloadOptional)
+		}
+		if test.cfg.NoStaticRemoteKey {
+			assertUnset(lnwire.StaticRemoteKeyOptional)
+		}
+
+		assertUnset(unknownFeature)
+	}
+
+	// Do same basic sanity checks on features that are always present.
+	nodeFeatures := m.Get(SetNodeAnn)
+
+	assertSet := func(bit lnwire.FeatureBit) {
+		has := nodeFeatures.HasFeature(bit)
+		if !has {
+			t.Fatalf("node features don't advertised %v", bit)
+		}
+	}
+
+	assertSet(lnwire.DataLossProtectOptional)
+	if !test.cfg.NoTLVOnion {
+		assertSet(lnwire.TLVOnionPayloadRequired)
+	}
+	if !test.cfg.NoStaticRemoteKey {
+		assertSet(lnwire.StaticRemoteKeyOptional)
+	}
+}
diff --git a/fundingmanager_test.go b/fundingmanager_test.go
index 024339d90..09bbe6605 100644
--- a/fundingmanager_test.go
+++ b/fundingmanager_test.go
@@ -184,11 +184,11 @@ func (n *testNode) QuitSignal() <-chan struct{} {
 	return n.shutdownChannel
 }
 
-func (n *testNode) LocalGlobalFeatures() *lnwire.FeatureVector {
+func (n *testNode) LocalFeatures() *lnwire.FeatureVector {
 	return lnwire.NewFeatureVector(nil, nil)
 }
 
-func (n *testNode) RemoteGlobalFeatures() *lnwire.FeatureVector {
+func (n *testNode) RemoteFeatures() *lnwire.FeatureVector {
 	return lnwire.NewFeatureVector(nil, nil)
 }
 
diff --git a/htlcswitch/link_test.go b/htlcswitch/link_test.go
index 93eb37635..96e2430b8 100644
--- a/htlcswitch/link_test.go
+++ b/htlcswitch/link_test.go
@@ -1653,10 +1653,10 @@ func (m *mockPeer) IdentityKey() *btcec.PublicKey {
 func (m *mockPeer) Address() net.Addr {
 	return nil
 }
-func (m *mockPeer) LocalGlobalFeatures() *lnwire.FeatureVector {
+func (m *mockPeer) LocalFeatures() *lnwire.FeatureVector {
 	return nil
 }
-func (m *mockPeer) RemoteGlobalFeatures() *lnwire.FeatureVector {
+func (m *mockPeer) RemoteFeatures() *lnwire.FeatureVector {
 	return nil
 }
 
diff --git a/routing/missioncontrol_test.go b/routing/missioncontrol_test.go
index f7b2f33a0..bbef69c94 100644
--- a/routing/missioncontrol_test.go
+++ b/routing/missioncontrol_test.go
@@ -13,7 +13,7 @@ import (
 
 var (
 	mcTestRoute = &route.Route{
-		SourcePubKey: mcTestSelf,
+		SourcePubKey: route.Vertex{10},
 		Hops: []*route.Hop{
 			{
 				ChannelID:     1,
@@ -30,7 +30,6 @@ var (
 	}
 
 	mcTestTime  = time.Date(2018, time.January, 9, 14, 00, 00, 0, time.UTC)
-	mcTestSelf  = route.Vertex{10}
 	mcTestNode1 = mcTestRoute.Hops[0].PubKeyBytes
 	mcTestNode2 = mcTestRoute.Hops[1].PubKeyBytes
 
@@ -81,7 +80,6 @@ func (ctx *mcTestContext) restartMc() {
 			PenaltyHalfLife:       testPenaltyHalfLife,
 			AprioriHopProbability: testAprioriHopProbability,
 			AprioriWeight:         testAprioriWeight,
-			SelfNode:              mcTestSelf,
 		},
 	)
 	if err != nil {
@@ -100,6 +98,7 @@ func (ctx *mcTestContext) cleanup() {
 
 // Assert that mission control returns a probability for an edge.
 func (ctx *mcTestContext) expectP(amt lnwire.MilliSatoshi, expected float64) {
+
 	ctx.t.Helper()
 
 	p := ctx.mc.GetProbability(mcTestNode1, mcTestNode2, amt)
@@ -139,13 +138,6 @@ func TestMissionControl(t *testing.T) {
 
 	testTime := time.Date(2018, time.January, 9, 14, 00, 00, 0, time.UTC)
 
-	// For local channels, we expect a higher probability than our a prior
-	// test probability.
-	selfP := ctx.mc.GetProbability(mcTestSelf, mcTestNode1, 100)
-	if selfP != prevSuccessProbability {
-		t.Fatalf("expected prev success prob for untried local chans")
-	}
-
 	// Initial probability is expected to be the a priori.
 	ctx.expectP(1000, testAprioriHopProbability)
 
diff --git a/routing/notifications_test.go b/routing/notifications_test.go
index a18f5c90c..2371a4e1c 100644
--- a/routing/notifications_test.go
+++ b/routing/notifications_test.go
@@ -28,7 +28,7 @@ var (
 		Port: 9000}
 	testAddrs = []net.Addr{testAddr}
 
-	testFeatures = lnwire.NewFeatureVector(nil, lnwire.GlobalFeatures)
+	testFeatures = lnwire.NewFeatureVector(nil, lnwire.Features)
 
 	testHash = [32]byte{
 		0xb7, 0x94, 0x38, 0x5f, 0x2d, 0x1e, 0xf7, 0xab,