feature: add new feature pkg to manage feature sets
This commit introduces a feature.Manager, which derives feature vectors for various contexts within the daemon. The sets can be described via a staticly compiled format, which makes any runtime adjustments to the feature sets when the manager is initialized.
This commit is contained in:
parent
90e36ca04b
commit
fe566e1755
32
feature/default_sets.go
Normal file
32
feature/default_sets.go
Normal file
@ -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: {},
|
||||||
|
},
|
||||||
|
}
|
97
feature/manager.go
Normal file
97
feature/manager.go
Normal file
@ -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)
|
||||||
|
}
|
128
feature/manager_internal_test.go
Normal file
128
feature/manager_internal_test.go
Normal file
@ -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)
|
||||||
|
}
|
||||||
|
}
|
41
feature/set.go
Normal file
41
feature/set.go
Normal file
@ -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"
|
||||||
|
}
|
||||||
|
}
|
@ -2,10 +2,17 @@ package lnwire
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"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
|
// 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
|
// 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
|
// "it's OK to be odd" rule, where features at even bit positions must be known
|
||||||
@ -136,6 +143,15 @@ func NewRawFeatureVector(bits ...FeatureBit) *RawFeatureVector {
|
|||||||
return fv
|
return fv
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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.
|
// IsSet returns whether a particular feature bit is enabled in the vector.
|
||||||
func (fv *RawFeatureVector) IsSet(feature FeatureBit) bool {
|
func (fv *RawFeatureVector) IsSet(feature FeatureBit) bool {
|
||||||
return fv.features[feature]
|
return fv.features[feature]
|
||||||
@ -146,6 +162,20 @@ func (fv *RawFeatureVector) Set(feature FeatureBit) {
|
|||||||
fv.features[feature] = true
|
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.
|
// Unset marks a feature as disabled in the vector.
|
||||||
func (fv *RawFeatureVector) Unset(feature FeatureBit) {
|
func (fv *RawFeatureVector) Unset(feature FeatureBit) {
|
||||||
delete(fv.features, feature)
|
delete(fv.features, feature)
|
||||||
|
Loading…
Reference in New Issue
Block a user